Decompiled source of ValheimUtils v0.0.19

ValheimUtilsByAi.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using ValheimUtilsByAi.Features;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("ValheimUtilsByAi")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("AI-generated quality-of-life tweaks for Valheim. Every feature is toggleable in the BepInEx config.")]
[assembly: AssemblyFileVersion("0.0.19.0")]
[assembly: AssemblyInformationalVersion("0.0.19+829dcb1489060afee8bad2af8fff859fb49795a1")]
[assembly: AssemblyProduct("ValheimUtilsByAi")]
[assembly: AssemblyTitle("ValheimUtilsByAi")]
[assembly: AssemblyVersion("0.0.19.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 ValheimUtilsByAi
{
	[BepInPlugin("mh.ValheimUtilsByAi", "ValheimUtilsByAi", "0.0.19")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	[SynchronizationMode(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log { get; private set; }

		internal static ConfigDescription Synced(string description, AcceptableValueBase? acceptableValues = null)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			return new ConfigDescription(description, acceptableValues, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} });
		}

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			CraftWithoutRoof.Bind(((BaseUnityPlugin)this).Config);
			KilnWoodFilter.Bind(((BaseUnityPlugin)this).Config);
			AutoStorage.Bind(((BaseUnityPlugin)this).Config);
			AutoMapSync.Bind(((BaseUnityPlugin)this).Config);
			FeedingTrough.Bind(((BaseUnityPlugin)this).Config);
			ForsakenPowerAnywhere.Bind(((BaseUnityPlugin)this).Config);
			AutoRepair.Bind(((BaseUnityPlugin)this).Config);
			StationBuildRange.Bind(((BaseUnityPlugin)this).Config);
			FastCorpseDrop.Bind(((BaseUnityPlugin)this).Config);
			CraftFromContainers.Bind(((BaseUnityPlugin)this).Config);
			ComfortRange.Bind(((BaseUnityPlugin)this).Config);
			ExtensionRange.Bind(((BaseUnityPlugin)this).Config);
			PortalMapTravel.Bind(((BaseUnityPlugin)this).Config);
			FarmEverything.Bind(((BaseUnityPlugin)this).Config);
			PlantGrowthTimer.Bind(((BaseUnityPlugin)this).Config);
			VehiclesOnMap.Bind(((BaseUnityPlugin)this).Config);
			BreedingLimit.Bind(((BaseUnityPlugin)this).Config);
			AutoDoors.Bind(((BaseUnityPlugin)this).Config);
			ModVersions.Bind(((BaseUnityPlugin)this).Config);
			LikedItems.Bind(((BaseUnityPlugin)this).Config);
			QuickStack.Bind(((BaseUnityPlugin)this).Config);
			StoreAll.Bind(((BaseUnityPlugin)this).Config);
			Restock.Bind(((BaseUnityPlugin)this).Config);
			SortItems.Bind(((BaseUnityPlugin)this).Config);
			TrashItems.Bind(((BaseUnityPlugin)this).Config);
			BiggerChests.Bind(((BaseUnityPlugin)this).Config);
			Harmony.CreateAndPatchAll(typeof(Plugin).Assembly, "mh.ValheimUtilsByAi");
			Log.LogInfo((object)"ValheimUtilsByAi 0.0.19 loaded");
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "mh.ValheimUtilsByAi";

		public const string PLUGIN_NAME = "ValheimUtilsByAi";

		public const string PLUGIN_VERSION = "0.0.19";
	}
}
namespace ValheimUtilsByAi.Features
{
	[HarmonyPatch(typeof(Door), "UpdateState")]
	internal static class AutoDoors
	{
		[HarmonyPatch(typeof(Game), "Shutdown")]
		private static class CloseOnShutdown
		{
			private static void Prefix()
			{
				//IL_0068: Unknown result type (might be due to invalid IL or missing references)
				foreach (Door item in Opened)
				{
					if (!((Object)(object)item == (Object)null) && NView.Invoke(item).IsValid() && NView.Invoke(item).GetZDO().GetInt(ZDOVars.s_state, 0) != 0 && CanInteract(item))
					{
						Open(item, ((Component)item).transform.forward);
					}
				}
				Opened.Clear();
			}
		}

		private const float OpenRange = 2.5f;

		private const float CloseRange = 4f;

		private static ConfigEntry<bool> _enabled = null;

		private static readonly HashSet<Door> Opened = new HashSet<Door>();

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

		private static readonly Func<Door, bool> CanInteract = AccessTools.MethodDelegate<Func<Door, bool>>(AccessTools.Method(typeof(Door), "CanInteract", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Action<Door, Vector3> Open = AccessTools.MethodDelegate<Action<Door, Vector3>>(AccessTools.Method(typeof(Door), "Open", (Type[])null, (Type[])null), (object)null, true);

		public static void Bind(ConfigFile config)
		{
			_enabled = config.Bind<bool>("Building", "AutoDoors", true, Plugin.Synced("Doors and gates you build open by themselves when you walk up to them and close once you've walked through. Doors in dungeons, crypts and other places the game generated are left alone."));
		}

		private static void Postfix(Door __instance)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			ZNetView val = NView.Invoke(__instance);
			if (!val.IsValid())
			{
				Opened.Remove(__instance);
				return;
			}
			Vector3 position = ((Component)__instance).transform.position;
			bool flag = val.GetZDO().GetInt(ZDOVars.s_state, 0) != 0;
			bool flag2 = !_enabled.Value || (Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting() || Vector3.Distance(((Component)localPlayer).transform.position, position) >= 4f;
			if (Opened.Contains(__instance))
			{
				if (flag2 && !OtherPlayerInRange(position))
				{
					if (flag && CanInteract(__instance))
					{
						Open(__instance, ((Component)__instance).transform.forward);
					}
					if (!flag || CanInteract(__instance))
					{
						Opened.Remove(__instance);
					}
				}
			}
			else
			{
				if (flag || flag2 || Vector3.Distance(((Component)localPlayer).transform.position, position) > 2.5f)
				{
					return;
				}
				Piece component = ((Component)__instance).GetComponent<Piece>();
				if (!((Object)(object)component == (Object)null) && component.GetCreator() != 0L && !((Object)(object)__instance.m_keyItem != (Object)null) && !__instance.m_canNotBeClosed && CanInteract(__instance) && (!__instance.m_checkGuardStone || PrivateArea.CheckAccess(position, 0f, false, false)))
				{
					Open(__instance, ((Component)localPlayer).transform.position - position);
					Opened.RemoveWhere((Door door) => (Object)(object)door == (Object)null);
					Opened.Add(__instance);
				}
			}
		}

		private static bool OtherPlayerInRange(Vector3 doorPos)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if ((Object)(object)allPlayer != (Object)(object)Player.m_localPlayer && !((Character)allPlayer).IsDead() && Vector3.Distance(((Component)allPlayer).transform.position, doorPos) < 4f)
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class AutoMapSync
	{
		[HarmonyPatch(typeof(Minimap), "Update")]
		private static class SyncWithServer
		{
			private static void Postfix(Minimap __instance, bool ___m_hasGenerated, List<PinData> ___m_pins, PinData? ___m_namePin)
			{
				if (!_enabled.Value || Game.m_noMap || (Object)(object)Player.m_localPlayer == (Object)null || !___m_hasGenerated)
				{
					return;
				}
				if (_pendingSharedMap != null && _ownPinsAtLastUpload != null)
				{
					ApplySharedMap(__instance);
				}
				if (!(Time.time < _nextUpload) && ___m_namePin == null && TryGetServer(out var server))
				{
					List<SharedPin> list = OwnPins(___m_pins);
					_nextUpload = Time.time + 300f;
					if (_ownPinsAtLastUpload == null || _exploredSinceUpload || !SamePins(list, _ownPinsAtLastUpload))
					{
						_exploredSinceUpload = false;
						_ownPinsAtLastUpload = list;
						_rpc.SendPackage(server, Pack(Utils.Compress(__instance.GetSharedMapData((byte[])null))));
					}
				}
			}
		}

		[HarmonyPatch(typeof(Minimap), "Explore", new Type[]
		{
			typeof(int),
			typeof(int)
		})]
		private static class NoteLocalExploration
		{
			private static void Postfix(bool __result)
			{
				if (__result)
				{
					_exploredSinceUpload = true;
				}
			}
		}

		[HarmonyPatch(typeof(Minimap), "ToggleIconFilter")]
		private static class KeepIconFiltersDuringSync
		{
			private static bool Prefix()
			{
				return !_applyingSharedMap;
			}
		}

		[HarmonyPatch(typeof(ZNet), "Awake")]
		private static class ResetOnWorldStart
		{
			private static void Postfix()
			{
				_sharedMap = new SharedMap();
				ResetClient();
			}
		}

		private sealed class SharedPin
		{
			public long Owner;

			public string Name = "";

			public Vector3 Pos;

			public int Type;

			public bool Checked;

			public string Author = "";

			public bool SameAs(SharedPin other)
			{
				//IL_0022: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Unknown result type (might be due to invalid IL or missing references)
				if (Owner == other.Owner && Name == other.Name && Pos == other.Pos && Type == other.Type)
				{
					return Checked == other.Checked;
				}
				return false;
			}
		}

		private sealed class SharedMap
		{
			private const int VanillaVersion = 3;

			private byte[]? _explored;

			private readonly List<SharedPin> _pins = new List<SharedPin>();

			private readonly HashSet<long> _ownersWhoUploaded = new HashSet<long>();

			private byte[]? _payload;

			public static bool TryReadUpload(byte[] compressed, out byte[] explored, out List<SharedPin> pins)
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Expected O, but got Unknown
				//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
				explored = Array.Empty<byte>();
				pins = new List<SharedPin>();
				try
				{
					byte[] array = Utils.Decompress(compressed);
					ZPackage val = new ZPackage(array);
					int num = val.ReadInt();
					if (num < 2 || num > 3)
					{
						throw new InvalidOperationException($"unknown version {num}");
					}
					int num2 = val.ReadInt();
					if (num2 <= 0 || num2 > array.Length)
					{
						throw new InvalidOperationException($"bad pixel count {num2}");
					}
					explored = val.ReadByteArray(num2);
					if (explored.Length != num2)
					{
						throw new InvalidOperationException("explored area is cut off");
					}
					int num3 = val.ReadInt();
					for (int i = 0; i < num3; i++)
					{
						pins.Add(new SharedPin
						{
							Owner = val.ReadLong(),
							Name = val.ReadString(),
							Pos = val.ReadVector3(),
							Type = val.ReadInt(),
							Checked = val.ReadBool(),
							Author = ((num >= 3) ? val.ReadString() : "")
						});
					}
					return true;
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("AutoMapSync: dropped an unreadable map upload: " + ex.Message));
					return false;
				}
			}

			public bool Merge(long uploader, byte[] explored, List<SharedPin> uploadedPins, out bool uploaderIsBehind)
			{
				uploaderIsBehind = false;
				if (_explored == null)
				{
					_explored = new byte[explored.Length];
				}
				if (_explored.Length != explored.Length)
				{
					Plugin.Log.LogWarning((object)$"AutoMapSync: dropped a map upload of {explored.Length} pixels, the shared map has {_explored.Length}");
					return false;
				}
				bool flag = false;
				for (int i = 0; i < explored.Length; i++)
				{
					if (explored[i] != 0)
					{
						if (_explored[i] == 0)
						{
							_explored[i] = 1;
							flag = true;
						}
					}
					else if (_explored[i] != 0)
					{
						uploaderIsBehind = true;
					}
				}
				if (uploader != 0L)
				{
					List<SharedPin> list = uploadedPins.Where((SharedPin sharedPin) => sharedPin.Owner == uploader).ToList();
					if (!SamePins(list, _pins.Where((SharedPin sharedPin) => sharedPin.Owner == uploader).ToList()))
					{
						_pins.RemoveAll((SharedPin sharedPin) => sharedPin.Owner == uploader);
						_pins.AddRange(list);
						flag = true;
					}
					_ownersWhoUploaded.Add(uploader);
				}
				foreach (SharedPin pin in uploadedPins)
				{
					if (pin.Owner != 0L && pin.Owner != uploader && !_ownersWhoUploaded.Contains(pin.Owner) && !_pins.Any((SharedPin known) => known.Owner == pin.Owner && Utils.DistanceXZ(known.Pos, pin.Pos) < 1f))
					{
						_pins.Add(pin);
						flag = true;
					}
				}
				uploaderIsBehind |= _pins.Any((SharedPin known) => !uploadedPins.Any((SharedPin sharedPin) => Utils.DistanceXZ(sharedPin.Pos, known.Pos) < 1f));
				if (flag)
				{
					_payload = null;
				}
				return flag;
			}

			public byte[]? Payload()
			{
				object obj;
				if (_explored != null)
				{
					obj = _payload;
					if (obj == null)
					{
						return _payload = Utils.Compress(Serialize(_explored));
					}
				}
				else
				{
					obj = null;
				}
				return (byte[]?)obj;
			}

			private byte[] Serialize(byte[] explored)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Expected O, but got Unknown
				//IL_0055: Unknown result type (might be due to invalid IL or missing references)
				ZPackage val = new ZPackage();
				val.Write(3);
				val.Write(explored);
				val.Write(_pins.Count);
				foreach (SharedPin pin in _pins)
				{
					val.Write(pin.Owner);
					val.Write(pin.Name);
					val.Write(pin.Pos);
					val.Write(pin.Type);
					val.Write(pin.Checked);
					val.Write(pin.Author);
				}
				return val.GetArray();
			}
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static CoroutineHandler <0>__OnServerReceive;

			public static CoroutineHandler <1>__OnClientReceive;
		}

		private const float UploadInterval = 300f;

		private static ConfigEntry<bool> _enabled = null;

		private static CustomRPC _rpc = null;

		private static SharedMap _sharedMap = new SharedMap();

		private static float _nextUpload;

		private static bool _exploredSinceUpload;

		private static List<SharedPin>? _ownPinsAtLastUpload;

		private static byte[]? _pendingSharedMap;

		private static bool _applyingSharedMap;

		public static void Bind(ConfigFile config)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			_enabled = config.Bind<bool>("World", "AutoMapSync", true, Plugin.Synced("All players share one map: explored areas and pins are merged on the server and passed on to everyone, no cartography table needed. Your map is sent when you join and then at most every 5 minutes, only after you explored something or changed your own pins."));
			_enabled.SettingChanged += delegate
			{
				ResetClient();
			};
			NetworkManager instance = NetworkManager.Instance;
			object obj = <>O.<0>__OnServerReceive;
			if (obj == null)
			{
				CoroutineHandler val = OnServerReceive;
				<>O.<0>__OnServerReceive = val;
				obj = (object)val;
			}
			object obj2 = <>O.<1>__OnClientReceive;
			if (obj2 == null)
			{
				CoroutineHandler val2 = OnClientReceive;
				<>O.<1>__OnClientReceive = val2;
				obj2 = (object)val2;
			}
			_rpc = instance.AddRPC("ValheimUtilsByAi.AutoMapSync", (CoroutineHandler)obj, (CoroutineHandler)obj2);
		}

		private static void ResetClient()
		{
			_nextUpload = 0f;
			_exploredSinceUpload = false;
			_ownPinsAtLastUpload = null;
			_pendingSharedMap = null;
		}

		private static IEnumerator OnServerReceive(long sender, ZPackage package)
		{
			byte[] array = package.ReadByteArray();
			if (_enabled.Value)
			{
				if (array.Length == 0)
				{
					SendSharedMap(sender);
				}
				else
				{
					MergeUpload(sender, array);
				}
			}
			yield break;
		}

		private static void MergeUpload(long sender, byte[] upload)
		{
			if (!SharedMap.TryReadUpload(upload, out byte[] explored, out List<SharedPin> pins))
			{
				return;
			}
			if (_sharedMap.Merge(PlayerIdOf(sender), explored, pins, out var uploaderIsBehind))
			{
				List<ZNetPeer> list = (from peer in ZNet.instance.GetPeers()
					where peer.m_uid != sender
					select peer).ToList();
				if (list.Count > 0)
				{
					_rpc.SendPackage(list, Pack(_sharedMap.Payload()));
				}
				if (!ZNet.instance.IsDedicated() && sender != ZNet.GetUID())
				{
					_pendingSharedMap = _sharedMap.Payload();
				}
			}
			if (uploaderIsBehind)
			{
				SendSharedMap(sender);
			}
		}

		private static void SendSharedMap(long target)
		{
			byte[] array = _sharedMap.Payload();
			if (array != null)
			{
				if (target == ZNet.GetUID())
				{
					_pendingSharedMap = array;
				}
				else
				{
					_rpc.SendPackage(target, Pack(array));
				}
			}
		}

		private static long PlayerIdOf(long sender)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			if (sender == ZNet.GetUID())
			{
				if (!((Object)(object)Player.m_localPlayer != (Object)null))
				{
					return 0L;
				}
				return Player.m_localPlayer.GetPlayerID();
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			ZDO obj = ((peer == null) ? null : ZDOMan.instance.GetZDO(peer.m_characterID));
			if (obj == null)
			{
				return 0L;
			}
			return obj.GetLong(ZDOVars.s_playerID, 0L);
		}

		private static IEnumerator OnClientReceive(long sender, ZPackage package)
		{
			if (_enabled.Value)
			{
				_pendingSharedMap = package.ReadByteArray();
			}
			yield break;
		}

		private static ZPackage Pack(byte[] data)
		{
			//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: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(data);
			return val;
		}

		private static bool TryGetServer(out long server)
		{
			server = 0L;
			if (ZNet.instance.IsServer())
			{
				if (ZNet.instance.GetPeers().Count == 0)
				{
					return false;
				}
				server = ZNet.GetUID();
				return true;
			}
			ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
			if (serverPeer == null || !ModCompatibility.IsModuleOnServer("mh.ValheimUtilsByAi"))
			{
				return false;
			}
			server = serverPeer.m_uid;
			return true;
		}

		private static void ApplySharedMap(Minimap minimap)
		{
			byte[] pendingSharedMap = _pendingSharedMap;
			_pendingSharedMap = null;
			_applyingSharedMap = true;
			try
			{
				minimap.AddSharedMapData(Utils.Decompress(pendingSharedMap));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("AutoMapSync: could not apply the shared map: " + ex.Message));
			}
			finally
			{
				_applyingSharedMap = false;
			}
		}

		private static List<SharedPin> OwnPins(List<PinData> pins)
		{
			return (from pin in pins
				where pin.m_save && pin.m_ownerID == 0L && (int)pin.m_type != 4
				select new SharedPin
				{
					Name = pin.m_name,
					Pos = pin.m_pos,
					Type = (int)pin.m_type,
					Checked = pin.m_checked
				}).ToList();
		}

		private static bool SamePins(List<SharedPin> a, List<SharedPin> b)
		{
			if (a.Count == b.Count)
			{
				return a.Zip(b, (SharedPin x, SharedPin y) => x.SameAs(y)).All((bool same) => same);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRepair")]
	internal static class AutoRepair
	{
		private static ConfigEntry<bool> _enabled = null;

		private static ConfigEntry<float> _nearbyMeters = null;

		private static readonly Func<InventoryGui, ItemData, bool> CanRepair = AccessTools.MethodDelegate<Func<InventoryGui, ItemData, bool>>(AccessTools.Method(typeof(InventoryGui), "CanRepair", (Type[])null, (Type[])null), (object)null, true);

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

		private static readonly FieldRef<Player, CraftingStation> CurrentStation = AccessTools.FieldRefAccess<Player, CraftingStation>("m_currentStation");

		private static readonly List<CraftingStation> AllStations = AccessTools.StaticFieldRefAccess<List<CraftingStation>>(typeof(CraftingStation), "m_allStations");

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

		private static readonly List<ItemData> Worn = new List<ItemData>();

		private static readonly List<ItemData> Repairable = new List<ItemData>();

		private static readonly List<CraftingStation> Stations = new List<CraftingStation>();

		private static float _idleUntil;

		public static void Bind(ConfigFile config)
		{
			_enabled = config.Bind<bool>("Crafting", "AutoRepair", true, Plugin.Synced("Opening a crafting station repairs every item it can repair, without clicking the repair button."));
			_nearbyMeters = config.Bind<float>("Crafting", "AutoRepairNearbyMeters", 15f, Plugin.Synced("Crafting stations within this many meters of you also repair what they can when you open one, each with its vanilla rules (item, station level, roof, fire). 0 repairs only at the station you open.", (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0f, 50f)));
		}

		private static void Postfix(InventoryGui __instance)
		{
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Unknown result type (might be due to invalid IL or missing references)
			//IL_028e: Unknown result type (might be due to invalid IL or missing references)
			//IL_029c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			if (!_enabled.Value || !((Component)__instance.m_repairButton).gameObject.activeSelf)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			Worn.Clear();
			((Humanoid)localPlayer).GetInventory().GetWornItems(Worn);
			Worn.RemoveAll((ItemData item) => !item.m_shared.m_canBeReparied);
			if (Worn.Count == 0 || Time.time < _idleUntil)
			{
				return;
			}
			CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation();
			Stations.Clear();
			Stations.Add(currentCraftingStation);
			float value = _nearbyMeters.Value;
			foreach (CraftingStation allStation in AllStations)
			{
				ZNetView val = StationView.Invoke(allStation);
				if ((Object)(object)allStation != (Object)(object)currentCraftingStation && allStation.m_canRepair && (Object)(object)val != (Object)null && val.IsValid() && Vector3.Distance(((Component)allStation).transform.position, ((Component)localPlayer).transform.position) < value)
				{
					Stations.Add(allStation);
				}
			}
			int num = 0;
			string text = "";
			foreach (CraftingStation station in Stations)
			{
				Repairable.Clear();
				CurrentStation.Invoke(localPlayer) = station;
				try
				{
					foreach (ItemData item in Worn)
					{
						if (CanRepair(__instance, item))
						{
							Repairable.Add(item);
						}
					}
				}
				finally
				{
					CurrentStation.Invoke(localPlayer) = currentCraftingStation;
				}
				if (Repairable.Count == 0 || ((Object)(object)station != (Object)null && !station.CheckUsable(localPlayer, false)))
				{
					continue;
				}
				foreach (ItemData item2 in Repairable)
				{
					((Character)localPlayer).RaiseSkill((SkillType)107, 1f - item2.m_durability / item2.GetMaxDurability());
					item2.m_durability = item2.GetMaxDurability();
					Worn.Remove(item2);
					if (num++ == 0)
					{
						text = item2.m_shared.m_name;
					}
				}
				if ((Object)(object)station != (Object)null)
				{
					station.m_repairItemDoneEffects.Create(((Component)station).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
				}
			}
			if (num == 0)
			{
				_idleUntil = Time.time + 0.5f;
				return;
			}
			((Character)localPlayer).Message((MessageType)2, (num == 1) ? Localization.instance.Localize("$msg_repaired", new string[1] { text }) : $"{num} items repaired", 0, (Sprite)null, false);
			UpdateCraftingPanel(__instance, arg2: false);
		}
	}
	internal static class AutoStorage
	{
		private enum Direction
		{
			Input,
			Output
		}

		[HarmonyPatch(typeof(Container), "Awake")]
		private static class RegisterChest
		{
			private static void Postfix(Container __instance)
			{
				if (__instance.GetInventory() == null)
				{
					return;
				}
				ZNetView val = ContainerView.Invoke(__instance);
				if (!((Object)(object)val == (Object)null) && val.IsValid() && !((Object)(object)((Component)__instance).GetComponent<Piece>() == (Object)null) && !((Object)(object)__instance.m_wagon != (Object)null) && !((Object)(object)__instance.m_rootObjectOverride != (Object)null) && !__instance.m_autoDestroyEmpty && !((Object)(object)((Component)__instance).GetComponentInParent<Ship>() != (Object)null) && !((Object)(object)((Component)__instance).GetComponentInParent<Incinerator>() != (Object)null))
				{
					Container chest = __instance;
					val.Register("mh_AutoStorage_RequestOwn", (Action<long>)delegate(long sender)
					{
						RPC_RequestOwn(chest, sender);
					});
					Chests.Add(chest);
				}
			}
		}

		[HarmonyPatch(typeof(Container), "SetInUse")]
		private static class FreshContentsOnOpen
		{
			private static void Prefix(Container __instance, bool inUse)
			{
				if (inUse && !__instance.IsInUse() && __instance.IsOwner())
				{
					ContainerLoad(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "Awake")]
		private static class RegisterSetLinkRpc
		{
			private static void Postfix()
			{
				ZRoutedRpc.instance.Register<ZDOID, int, bool, Vector3>("mh_AutoStorage_SetLink", (Method<ZDOID, int, bool, Vector3>)RPC_SetLink);
			}
		}

		[HarmonyPatch(typeof(Smelter), "UpdateSmelter")]
		private static class FeedSmelter
		{
			private static void Postfix(Smelter __instance)
			{
				ZNetView val = SmelterView.Invoke(__instance);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				IReadOnlyList<Inventory> readOnlyList = LinkedInventories(val, Direction.Output);
				if (readOnlyList.Count > 0 && CollectDue(val))
				{
					CollectStrays(val, readOnlyList, __instance.m_conversion.Select((ItemConversion conversion) => conversion.m_to));
				}
				IReadOnlyList<Inventory> readOnlyList2 = LinkedInventories(val, Direction.Input);
				if (readOnlyList2.Count == 0)
				{
					return;
				}
				ZDO zDO = val.GetZDO();
				if (__instance.m_maxFuel > 0 && (Object)(object)__instance.m_fuelItem != (Object)null)
				{
					float num;
					for (num = zDO.GetFloat(ZDOVars.s_fuel, 0f); num <= (float)(__instance.m_maxFuel - 1); num += 1f)
					{
						if (!TakeOne(readOnlyList2, __instance.m_fuelItem))
						{
							break;
						}
					}
					zDO.Set(ZDOVars.s_fuel, num);
				}
				while (zDO.GetInt(ZDOVars.s_queued, 0) < __instance.m_maxOre)
				{
					ItemData val2 = TakeCookable(readOnlyList2, __instance, SmelterFindCookable);
					if (val2 == null)
					{
						break;
					}
					SmelterQueueOre(__instance, ((Object)val2.m_dropPrefab).name, val2.m_cheated);
				}
			}
		}

		[HarmonyPatch(typeof(Smelter), "Spawn")]
		private static class CollectSmelterOutput
		{
			private static bool Prefix(Smelter __instance, string ore, int stack)
			{
				ZNetView val = SmelterView.Invoke(__instance);
				if ((Object)(object)val == (Object)null)
				{
					return true;
				}
				IReadOnlyList<Inventory> readOnlyList = LinkedInventories(val, Direction.Output);
				if (readOnlyList.Count == 0)
				{
					return true;
				}
				ItemDrop val2 = SmelterConversion(__instance, ore)?.m_to;
				if (!((Object)(object)val2 == (Object)null))
				{
					return !Store(readOnlyList, ((Component)val2).gameObject, stack);
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(Beehive), "UpdateBees")]
		private static class CollectHoney
		{
			private static void Postfix(Beehive __instance)
			{
				ZNetView val = BeehiveView.Invoke(__instance);
				if ((Object)(object)val != (Object)null && (Object)(object)__instance.m_honeyItem != (Object)null)
				{
					CollectLevel(val, __instance.m_honeyItem);
				}
			}
		}

		[HarmonyPatch(typeof(SapCollector), "UpdateTick")]
		private static class CollectSap
		{
			private static void Postfix(SapCollector __instance)
			{
				ZNetView val = SapCollectorView.Invoke(__instance);
				if (!((Object)(object)val == (Object)null) && !((Object)(object)__instance.m_spawnItem == (Object)null))
				{
					int num = (val.IsValid() ? val.GetZDO().GetInt(ZDOVars.s_level, 0) : 0);
					CollectLevel(val, __instance.m_spawnItem);
					if (num > 0 && val.GetZDO().GetInt(ZDOVars.s_level, 0) == 0)
					{
						val.InvokeRPC(ZNetView.Everybody, "RPC_UpdateEffects", Array.Empty<object>());
					}
				}
			}
		}

		[HarmonyPatch(typeof(CookingStation), "UpdateCooking")]
		private static class FeedCookingStation
		{
			private static void Postfix(CookingStation __instance)
			{
				ZNetView val = CookingView.Invoke(__instance);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				ZDO zDO = val.GetZDO();
				IReadOnlyList<Inventory> readOnlyList = LinkedInventories(val, Direction.Output);
				if (readOnlyList.Count > 0)
				{
					if (CollectDue(val))
					{
						CollectStrays(val, readOnlyList, __instance.m_conversion.Select((ItemConversion conversion) => conversion.m_to).Concat((IEnumerable<ItemDrop>)(object)new ItemDrop[1] { __instance.m_overCookedItem }));
					}
					for (int num = 0; num < __instance.m_slots.Length; num++)
					{
						string text = zDO.GetString("slot" + num, "");
						if (!(text == "") && CookingIsItemDone(__instance, text))
						{
							GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text);
							if (!((Object)(object)itemPrefab == (Object)null) && Store(readOnlyList, itemPrefab, 1))
							{
								CookingSetSlot.Invoke(__instance, new object[5]
								{
									num,
									"",
									0f,
									Enum.ToObject(CookingStatus, 0),
									false
								});
								val.InvokeRPC(ZNetView.Everybody, "RPC_SetSlotVisual", new object[2] { num, "" });
							}
						}
					}
				}
				IReadOnlyList<Inventory> readOnlyList2 = LinkedInventories(val, Direction.Input);
				if (readOnlyList2.Count == 0)
				{
					return;
				}
				if (__instance.m_useFuel && (Object)(object)__instance.m_fuelItem != (Object)null)
				{
					float num2;
					for (num2 = zDO.GetFloat(ZDOVars.s_fuel, 0f); num2 <= (float)(__instance.m_maxFuel - 1); num2 += 1f)
					{
						if (!TakeOne(readOnlyList2, __instance.m_fuelItem))
						{
							break;
						}
					}
					zDO.Set(ZDOVars.s_fuel, num2);
				}
				if (__instance.m_requireFire && !CookingIsFireLit(__instance))
				{
					return;
				}
				if (readOnlyList.Count == 0)
				{
					List<Vector3> list = ReadChestPositions(zDO, Direction.Output);
					if (list != null && list.Count > 0)
					{
						return;
					}
				}
				while (CookingFreeSlot(__instance) >= 0)
				{
					ItemData val2 = TakeCookable(readOnlyList2, __instance, CookingFindCookable);
					if (val2 == null)
					{
						break;
					}
					val.InvokeRPC("RPC_AddItem", new object[2]
					{
						((Object)val2.m_dropPrefab).name,
						val2.m_cheated
					});
				}
			}
		}

		[HarmonyPatch(typeof(Fermenter), "SlowUpdate")]
		private static class FeedFermenter
		{
			private static void Postfix(Fermenter __instance)
			{
				ZNetView val = FermenterView.Invoke(__instance);
				if ((Object)(object)val == (Object)null || !val.IsValid())
				{
					return;
				}
				ZDO zDO = val.GetZDO();
				IReadOnlyList<Inventory> readOnlyList = LinkedInventories(val, Direction.Output);
				if (readOnlyList.Count > 0 && CollectDue(val))
				{
					CollectStrays(val, readOnlyList, __instance.m_conversion.Select((ItemConversion conversion) => conversion.m_to));
				}
				int num = zDO.GetInt(ZDOVars.s_content, 0);
				if (num == 0)
				{
					ItemData val2 = TakeCookable(LinkedInventories(val, Direction.Input), __instance, FermenterFindCookable);
					if (val2 != null)
					{
						val.InvokeRPC("RPC_AddItem", new object[2]
						{
							StringExtensionMethods.GetStableHashCode(((Object)val2.m_dropPrefab).name),
							val2.m_cheated
						});
					}
				}
				else
				{
					if (readOnlyList.Count == 0)
					{
						return;
					}
					long num2 = zDO.GetLong(ZDOVars.s_startTime, 0L);
					if (num2 == 0L || (ZNet.instance.GetTime() - new DateTime(num2)).TotalSeconds <= (double)__instance.m_fermentationDuration)
					{
						return;
					}
					foreach (ItemConversion item in __instance.m_conversion)
					{
						if (!((Object)(object)item.m_from == (Object)null) && StringExtensionMethods.GetStableHashCode(((Object)((Component)item.m_from).gameObject).name) == num)
						{
							if (Store(readOnlyList, ((Component)item.m_to).gameObject, item.m_producedItems))
							{
								zDO.Set(ZDOVars.s_content, 0, false);
								zDO.Set(ZDOVars.s_startTime, 0L);
								zDO.Set(ZDOVars.s_cheatedQueued, false);
							}
							break;
						}
					}
				}
			}
		}

		[HarmonyPatch(typeof(Player), "SetupPlacementGhost")]
		private static class NoLinkGhost
		{
			private static bool Prefix(Player __instance)
			{
				if (!IsLinkTool(__instance.GetSelectedPiece()))
				{
					return true;
				}
				ref GameObject reference = ref PlacementGhost.Invoke(__instance);
				if ((Object)(object)reference != (Object)null)
				{
					Object.Destroy((Object)(object)reference);
				}
				reference = null;
				return false;
			}
		}

		[HarmonyPatch(typeof(Player), "UpdatePlacement")]
		private static class LinkClick
		{
			private static void Postfix(Player __instance, bool takeInput)
			{
				if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || !takeInput || ((Character)__instance).IsDead())
				{
					return;
				}
				Direction? direction = HeldLinkTool(__instance);
				if (direction.HasValue && !Hud.IsPieceSelectionVisible() && !Hud.InRadial() && !ZInput.GetButton("JoyAltKeys") && (ZInput.GetButtonDown("Attack") || ZInput.GetButtonDown("JoyPlace")))
				{
					if (!_enabled.Value)
					{
						((Character)__instance).Message((MessageType)2, "AutoStorage is disabled in the config", 0, (Sprite)null, false);
					}
					else
					{
						HandleHammerClick(__instance, direction.Value);
					}
				}
			}
		}

		[HarmonyPatch(typeof(ObjectDB), "GetAllBuildPieces")]
		private static class HideLinkToolsFromAchievement
		{
			private static void Postfix(List<Piece> __result)
			{
				if ((Object)(object)_inputTool != (Object)null)
				{
					__result.Remove(_inputTool);
				}
				if ((Object)(object)_outputTool != (Object)null)
				{
					__result.Remove(_outputTool);
				}
			}
		}

		[HarmonyPatch(typeof(Player), "OnSpawned")]
		private static class OldHammerOnSpawn
		{
			private static void Postfix(Player __instance)
			{
				_checkOldHammers |= (Object)(object)__instance == (Object)(object)Player.m_localPlayer;
			}
		}

		[HarmonyPatch(typeof(Player), "OnInventoryChanged")]
		private static class OldHammerOnChange
		{
			private static void Postfix(Player __instance)
			{
				_checkOldHammers |= (Object)(object)__instance == (Object)(object)Player.m_localPlayer;
			}
		}

		[HarmonyPatch(typeof(Player), "Update")]
		private static class DrawLinks
		{
			private static void Postfix(Player __instance)
			{
				//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
				//IL_010d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0112: Unknown result type (might be due to invalid IL or missing references)
				//IL_0156: Unknown result type (might be due to invalid IL or missing references)
				//IL_015b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0165: Unknown result type (might be due to invalid IL or missing references)
				//IL_016a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0175: Unknown result type (might be due to invalid IL or missing references)
				//IL_017a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0184: Unknown result type (might be due to invalid IL or missing references)
				//IL_0189: Unknown result type (might be due to invalid IL or missing references)
				//IL_018e: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
				{
					return;
				}
				if (_checkOldHammers)
				{
					_checkOldHammers = false;
					RefundOldHammers(__instance);
				}
				Direction? direction = (_enabled.Value ? HeldLinkTool(__instance) : ((Direction?)null));
				if (direction != _toolInHand)
				{
					_toolInHand = direction;
					_selectedPiece = null;
					_nextLinkScan = 0f;
				}
				if (!direction.HasValue)
				{
					HideLines(0);
					return;
				}
				if (Time.time >= _nextLinkScan)
				{
					_nextLinkScan = Time.time + 0.5f;
					ScanLinks(((Component)__instance).transform.position, direction.Value);
				}
				int fromIndex = 0;
				Color color = ((direction == Direction.Input) ? InputLineColor : OutputLineColor);
				for (int i = 0; i + 1 < LinkSegments.Count; i += 2)
				{
					DrawLine(fromIndex++, LinkSegments[i], LinkSegments[i + 1], color);
				}
				if ((Object)(object)_selectedPiece != (Object)null && _selectedPiece.IsValid())
				{
					DrawLine(fromIndex++, ((Component)_selectedPiece).transform.position + Vector3.up * 0.5f, ((Component)__instance).transform.position + Vector3.up * 1.2f, Color.yellow);
				}
				HideLines(fromIndex);
			}
		}

		private const string HammerPrefab = "AutoStorageHammer";

		private const string HammerToken = "$item_autostoragehammer";

		private const float ChestMatchRadius = 0.5f;

		private static readonly int InputChestsKey = StringExtensionMethods.GetStableHashCode("mh_AutoStorageInputs");

		private static readonly int OutputChestsKey = StringExtensionMethods.GetStableHashCode("mh_AutoStorageOutputs");

		private static readonly int LegacyLinkKey = StringExtensionMethods.GetStableHashCode("mh_AutoStorageLink");

		private static ConfigEntry<bool> _enabled = null;

		private static ConfigEntry<float> _maxLinkDistance = null;

		private static ConfigEntry<float> _collectRadius = null;

		private static Piece? _inputTool;

		private static Piece? _outputTool;

		private const string RequestOwnRpc = "mh_AutoStorage_RequestOwn";

		private static readonly List<Container> Chests = new List<Container>();

		private static readonly Dictionary<Container, float> NextRequest = new Dictionary<Container, float>();

		private const string SetLinkRpc = "mh_AutoStorage_SetLink";

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

		private static readonly Func<Container, bool> ContainerLoad = AccessTools.MethodDelegate<Func<Container, bool>>(AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null), (object)null, true);

		private const float CollectInterval = 60f;

		private static readonly Dictionary<ZNetView, float> NextCollect = new Dictionary<ZNetView, float>();

		private static readonly Collider[] ItemHits = (Collider[])(object)new Collider[128];

		private static int _itemMask;

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

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

		private static readonly Func<Smelter, Inventory, ItemData?> SmelterFindCookable = AccessTools.MethodDelegate<Func<Smelter, Inventory, ItemData>>(AccessTools.Method(typeof(Smelter), "FindCookableItem", (Type[])null, (Type[])null), (object)null, true);

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

		private static readonly Func<Smelter, string, ItemConversion?> SmelterConversion = AccessTools.MethodDelegate<Func<Smelter, string, ItemConversion>>(AccessTools.Method(typeof(Smelter), "GetItemConversion", (Type[])null, (Type[])null), (object)null, true);

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

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

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

		private static readonly Func<CookingStation, bool> CookingIsFireLit = AccessTools.MethodDelegate<Func<CookingStation, bool>>(AccessTools.Method(typeof(CookingStation), "IsFireLit", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Func<CookingStation, int> CookingFreeSlot = AccessTools.MethodDelegate<Func<CookingStation, int>>(AccessTools.Method(typeof(CookingStation), "GetFreeSlot", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Func<CookingStation, Inventory, ItemData?> CookingFindCookable = AccessTools.MethodDelegate<Func<CookingStation, Inventory, ItemData>>(AccessTools.Method(typeof(CookingStation), "FindCookableItem", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Func<CookingStation, string, bool> CookingIsItemDone = AccessTools.MethodDelegate<Func<CookingStation, string, bool>>(AccessTools.Method(typeof(CookingStation), "IsItemDone", (Type[])null, (Type[])null), (object)null, true);

		private static readonly MethodInfo CookingSetSlot = AccessTools.Method(typeof(CookingStation), "SetSlot", (Type[])null, (Type[])null);

		private static readonly Type CookingStatus = AccessTools.Inner(typeof(CookingStation), "Status");

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

		private static readonly Func<Fermenter, Inventory, ItemData?> FermenterFindCookable = AccessTools.MethodDelegate<Func<Fermenter, Inventory, ItemData>>(AccessTools.Method(typeof(Fermenter), "FindCookableItem", (Type[])null, (Type[])null), (object)null, true);

		private static ZNetView? _selectedPiece;

		private static Direction? _toolInHand;

		private static readonly FieldRef<Player, GameObject?> PlacementGhost = AccessTools.FieldRefAccess<Player, GameObject>("m_placementGhost");

		private static bool _checkOldHammers;

		private static readonly Color InputLineColor = new Color(0.25f, 0.55f, 1f);

		private static readonly Color OutputLineColor = Color.green;

		private static readonly List<LineRenderer> Lines = new List<LineRenderer>();

		private static readonly List<Piece> NearbyPieces = new List<Piece>();

		private static readonly List<Vector3> LinkSegments = new List<Vector3>();

		private static float _nextLinkScan;

		private static Material? _lineMaterial;

		public static void Bind(ConfigFile config)
		{
			_enabled = config.Bind<bool>("Crafting", "AutoStorage", true, Plugin.Synced("Adds the AutoStorage Link input and AutoStorage Link output hammer tools. A processing piece (kiln, smelter, blast furnace, spinning wheel, windmill, eitr refinery, frigid kiln, hot tub, beehive, birds' nest, sap extractor, cooking stations, frost foundry, fermenter) takes its inputs from the chests linked as its input and puts its products into the chests linked as its output; one chest can be both. When this is off the automation and linking stop; links are kept. In multiplayer the setting of the game that controls the piece applies."));
			_maxLinkDistance = config.Bind<float>("Crafting", "AutoStorageMaxLinkDistance", 100f, Plugin.Synced("Maximum distance in meters between a piece and a chest linked to it. Pieces farther than roughly 64 m from any player are not simulated by the game, so a chest beyond that distance from the piece may simply not be loaded."));
			_collectRadius = config.Bind<float>("Crafting", "AutoStorageCollectRadius", 5f, Plugin.Synced("Once a minute, items of the kinds a linked piece makes lying on the ground within this many meters of it (for example because its output chests were full or open) are moved into its output chests. 0 turns this off."));
			PrefabManager.OnVanillaPrefabsAvailable += RegisterPrefabs;
		}

		private static void RegisterPrefabs()
		{
			//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_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Expected O, but got Unknown
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			PrefabManager.OnVanillaPrefabsAvailable -= RegisterPrefabs;
			_inputTool = AddLinkTool("AutoStorageLinkInput", "piece_autostoragelinkinput", "AutoStorage Link input", "CopperOre", "Click a processing piece, then a chest it should take its inputs from (ore, wood, fuel, raw food, mead base). Click the pair again to unlink. Click a chest with nothing selected to unlink it from every piece's inputs.");
			_outputTool = AddLinkTool("AutoStorageLinkOutput", "piece_autostoragelinkoutput", "AutoStorage Link output", "Copper", "Click a processing piece, then a chest it should put its products into. Click the pair again to unlink. Click a chest with nothing selected to unlink it from every piece's outputs.");
			CustomItem val = new CustomItem("AutoStorageHammer", "Hammer", new ItemConfig
			{
				Name = "$item_autostoragehammer",
				Description = "$item_autostoragehammer_description"
			});
			SharedData shared = val.ItemDrop.m_itemData.m_shared;
			shared.m_buildPieces = null;
			shared.m_useDurability = false;
			ItemManager.Instance.AddItem(val);
			CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
			string text = "$item_autostoragehammer".TrimStart(new char[1] { '$' });
			localization.AddTranslation(ref text, "AutoStorage Hammer");
			text = "item_autostoragehammer_description";
			localization.AddTranslation(ref text, "Replaced by AutoStorage Link input and AutoStorage Link output in the Hammer's Misc tab. Refunded automatically.");
		}

		private static Piece AddLinkTool(string prefabName, string token, string name, string iconItem, string description)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			PieceConfig val = new PieceConfig();
			val.Name = "$" + token;
			val.Description = "$" + token + "_description";
			val.PieceTable = PieceTables.Hammer;
			val.Category = PieceCategories.Misc;
			val.Usage = new string[1] { PieceUsages.Misc };
			val.Icon = ItemIcon(iconItem);
			CustomPiece val2 = new CustomPiece(prefabName, "piece_repair", val);
			val2.Piece.m_repairPiece = false;
			val2.Piece.m_removePiece = false;
			val2.Piece.m_canRotate = false;
			val2.Piece.m_resources = Array.Empty<Requirement>();
			val2.Piece.m_craftingStation = null;
			PieceManager.Instance.AddPiece(val2);
			CustomLocalization localization = LocalizationManager.Instance.GetLocalization();
			localization.AddTranslation(ref token, name);
			string text = token + "_description";
			localization.AddTranslation(ref text, description);
			return val2.Piece;
		}

		private static Sprite? ItemIcon(string itemPrefab)
		{
			GameObject prefab = PrefabManager.Instance.GetPrefab(itemPrefab);
			ItemDrop val = (((Object)(object)prefab == (Object)null) ? null : prefab.GetComponent<ItemDrop>());
			if (!((Object)(object)val == (Object)null))
			{
				return val.m_itemData.GetIcon();
			}
			return null;
		}

		private static void RPC_RequestOwn(Container chest, long sender)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = ContainerView.Invoke(chest);
			if (val.IsValid() && val.IsOwner() && !chest.IsInUse())
			{
				ZDOMan.instance.ForceSendZDO(sender, val.GetZDO().m_uid);
				val.GetZDO().SetOwner(sender);
			}
		}

		private static void RequestOwnership(Container chest, ZNetView view)
		{
			long owner = view.GetZDO().GetOwner();
			if (owner != 0L && (!NextRequest.TryGetValue(chest, out var value) || !(Time.time < value)))
			{
				NextRequest[chest] = Time.time + 5f;
				view.InvokeRPC(owner, "mh_AutoStorage_RequestOwn", Array.Empty<object>());
			}
		}

		private static Container? FindChestAt(Vector3 position)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			for (int num = Chests.Count - 1; num >= 0; num--)
			{
				Container val = Chests[num];
				if ((Object)(object)val == (Object)null)
				{
					Chests.RemoveAt(num);
					NextRequest.Remove(val);
				}
				else if (Vector3.Distance(((Component)val).transform.position, position) <= 0.5f)
				{
					return val;
				}
			}
			return null;
		}

		private static int ChestsKey(Direction direction)
		{
			if (direction != Direction.Input)
			{
				return OutputChestsKey;
			}
			return InputChestsKey;
		}

		private static List<Vector3>? ReadChestPositions(ZDO zdo, Direction direction)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			byte[] byteArray = zdo.GetByteArray(ChestsKey(direction), (byte[])null);
			if (byteArray == null)
			{
				return null;
			}
			ZPackage val = new ZPackage(byteArray);
			int num = val.ReadInt();
			List<Vector3> list = new List<Vector3>(num);
			for (int i = 0; i < num; i++)
			{
				list.Add(val.ReadVector3());
			}
			return list;
		}

		private static void WriteChestPositions(ZDO zdo, Direction direction, List<Vector3> positions)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = new ZPackage();
			val.Write(positions.Count);
			foreach (Vector3 position in positions)
			{
				val.Write(position);
			}
			zdo.Set(ChestsKey(direction), val.GetArray());
		}

		private static void SendLinkEdit(ZNetView piece, Direction direction, bool add, Vector3 chestPosition)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			ZDO zDO = piece.GetZDO();
			if (zDO.GetOwner() == 0L)
			{
				piece.ClaimOwnership();
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(zDO.GetOwner(), "mh_AutoStorage_SetLink", new object[4]
			{
				zDO.m_uid,
				(int)direction,
				add,
				chestPosition
			});
		}

		private static void RPC_SetLink(long sender, ZDOID pieceId, int direction, bool add, Vector3 chestPosition)
		{
			//IL_0005: 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_0036: Unknown result type (might be due to invalid IL or missing references)
			ZDO zDO = ZDOMan.instance.GetZDO(pieceId);
			if (zDO != null && zDO.IsOwner())
			{
				List<Vector3> list = ReadChestPositions(zDO, (Direction)direction) ?? new List<Vector3>();
				bool flag = RemoveChest(list, chestPosition);
				if (add)
				{
					list.Add(chestPosition);
				}
				if (add || flag)
				{
					WriteChestPositions(zDO, (Direction)direction, list);
				}
			}
		}

		private static bool RemoveChest(List<Vector3> positions, Vector3 chestPosition)
		{
			//IL_0007: 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)
			return positions.RemoveAll((Vector3 position) => Vector3.Distance(position, chestPosition) <= 0.5f) > 0;
		}

		private static IReadOnlyList<Inventory> LinkedInventories(ZNetView pieceView, Direction direction)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			if (!_enabled.Value || !pieceView.IsValid() || !pieceView.IsOwner())
			{
				return Array.Empty<Inventory>();
			}
			ZDO zDO = pieceView.GetZDO();
			if (zDO.GetVec3(LegacyLinkKey, Vector3.zero) != Vector3.zero)
			{
				zDO.Set(LegacyLinkKey, Vector3.zero);
			}
			List<Vector3> list = ReadChestPositions(zDO, direction);
			if (list == null || list.Count == 0)
			{
				return Array.Empty<Inventory>();
			}
			List<Inventory> list2 = new List<Inventory>(list.Count);
			foreach (Vector3 item in list)
			{
				if (Vector3.Distance(item, ((Component)pieceView).transform.position) > _maxLinkDistance.Value)
				{
					continue;
				}
				Container val = FindChestAt(item);
				if ((Object)(object)val == (Object)null || val.IsInUse())
				{
					continue;
				}
				ZNetView val2 = ContainerView.Invoke(val);
				if (!((Object)(object)val2 == (Object)null) && val2.IsValid())
				{
					if (!val2.IsOwner())
					{
						RequestOwnership(val, val2);
						continue;
					}
					CraftFromContainers.LoadChest(val);
					list2.Add(val.GetInventory());
				}
			}
			return list2;
		}

		private static bool TakeOne(IReadOnlyList<Inventory> inputs, ItemDrop item)
		{
			string name = item.m_itemData.m_shared.m_name;
			foreach (Inventory input in inputs)
			{
				if (LikedItems.RemoveAvailable(input, name, 1) == 1)
				{
					return true;
				}
			}
			return false;
		}

		private static ItemData? TakeCookable<TPiece>(IReadOnlyList<Inventory> inputs, TPiece piece, Func<TPiece, Inventory, ItemData?> findCookable)
		{
			foreach (Inventory inventory in inputs)
			{
				ItemData val = LikedItems.SkippingReserved(() => findCookable(piece, inventory));
				if (val != null && inventory.RemoveOneItem(val))
				{
					return val;
				}
			}
			return null;
		}

		private static bool Store(IReadOnlyList<Inventory> outputs, GameObject prefab, int amount)
		{
			if (amount <= 0)
			{
				return false;
			}
			ItemData val = prefab.GetComponent<ItemDrop>().m_itemData.Clone();
			val.m_dropPrefab = prefab;
			val.m_stack = amount;
			val.m_worldLevel = (byte)Game.m_worldLevel;
			foreach (Inventory output in outputs)
			{
				if (output.CanAddItem(val, -1))
				{
					return output.AddItem(val);
				}
			}
			return false;
		}

		private static bool CollectDue(ZNetView pieceView)
		{
			if (_collectRadius.Value <= 0f)
			{
				return false;
			}
			float time = Time.time;
			if (NextCollect.TryGetValue(pieceView, out var value))
			{
				if (time < value)
				{
					return false;
				}
			}
			else
			{
				foreach (ZNetView item in NextCollect.Keys.Where((ZNetView view) => (Object)(object)view == (Object)null).ToList())
				{
					NextCollect.Remove(item);
				}
			}
			NextCollect[pieceView] = time + 60f;
			return true;
		}

		private static void CollectStrays(ZNetView pieceView, IReadOnlyList<Inventory> outputs, IEnumerable<ItemDrop?> products)
		{
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> hashSet = new HashSet<string>(from product in products
				where (Object)(object)product != (Object)null
				select product.m_itemData.m_shared.m_name);
			if (hashSet.Count == 0)
			{
				return;
			}
			if (_itemMask == 0)
			{
				_itemMask = LayerMask.GetMask(new string[1] { "item" });
			}
			int num = Physics.OverlapSphereNonAlloc(((Component)pieceView).transform.position, _collectRadius.Value, ItemHits, _itemMask);
			for (int num2 = 0; num2 < num; num2++)
			{
				Rigidbody attachedRigidbody = ItemHits[num2].attachedRigidbody;
				ItemDrop val = (((Object)(object)attachedRigidbody == (Object)null) ? null : ((Component)attachedRigidbody).GetComponent<ItemDrop>());
				if ((Object)(object)val == (Object)null || val.IsPiece())
				{
					continue;
				}
				ZNetView val2 = ItemView.Invoke(val);
				if ((Object)(object)val2 == (Object)null || !val2.IsValid())
				{
					continue;
				}
				string name = val.m_itemData.m_shared.m_name;
				if (!hashSet.Contains(name))
				{
					continue;
				}
				if (!val2.IsOwner())
				{
					val.RequestOwn();
				}
				else
				{
					if (!val.CanPickup(true))
					{
						continue;
					}
					val.Load();
					if (val.m_itemData.m_pickedUp)
					{
						continue;
					}
					int stack = val.m_itemData.m_stack;
					int num3 = stack;
					foreach (Inventory output in outputs)
					{
						ItemData val3 = val.m_itemData.Clone();
						val3.m_stack = num3;
						int num4 = output.CountItems(name, -1, false);
						output.AddItem(val3);
						num3 -= output.CountItems(name, -1, false) - num4;
						if (num3 <= 0)
						{
							break;
						}
					}
					if (num3 <= 0)
					{
						ZNetScene.instance.Destroy(((Component)val).gameObject);
					}
					else if (num3 < stack)
					{
						val.SetStack(num3);
					}
				}
			}
		}

		private static void CollectLevel(ZNetView nview, ItemDrop item)
		{
			IReadOnlyList<Inventory> readOnlyList = LinkedInventories(nview, Direction.Output);
			if (readOnlyList.Count == 0)
			{
				return;
			}
			if (CollectDue(nview))
			{
				CollectStrays(nview, readOnlyList, (IEnumerable<ItemDrop?>)(object)new ItemDrop[1] { item });
			}
			ZDO zDO = nview.GetZDO();
			int num = zDO.GetInt(ZDOVars.s_level, 0);
			if (num > 0)
			{
				int amount = num * Game.instance.ScaleDrops(item.m_itemData, 1);
				if (Store(readOnlyList, ((Component)item).gameObject, amount))
				{
					zDO.Set(ZDOVars.s_level, 0, false);
				}
			}
		}

		private static Direction? HeldLinkTool(Player player)
		{
			if (!((Character)player).InPlaceMode())
			{
				return null;
			}
			Piece selectedPiece = player.GetSelectedPiece();
			if ((Object)(object)selectedPiece == (Object)null)
			{
				return null;
			}
			if ((Object)(object)selectedPiece == (Object)(object)_inputTool)
			{
				return Direction.Input;
			}
			if ((Object)(object)selectedPiece == (Object)(object)_outputTool)
			{
				return Direction.Output;
			}
			return null;
		}

		private static bool IsLinkTool(Piece? piece)
		{
			if ((Object)(object)piece != (Object)null)
			{
				if (!((Object)(object)piece == (Object)(object)_inputTool))
				{
					return (Object)(object)piece == (Object)(object)_outputTool;
				}
				return true;
			}
			return false;
		}

		private static ZNetView? LinkablePiece(GameObject hovered, Direction direction)
		{
			if ((Object)(object)hovered.GetComponentInParent<SiegeMachine>() != (Object)null)
			{
				return null;
			}
			Component componentInParent = (Component)(object)hovered.GetComponentInParent<Smelter>();
			if (componentInParent == null)
			{
				componentInParent = (Component)(object)hovered.GetComponentInParent<CookingStation>();
			}
			if (componentInParent == null)
			{
				componentInParent = (Component)(object)hovered.GetComponentInParent<Fermenter>();
			}
			if (direction == Direction.Output)
			{
				if (componentInParent == null)
				{
					componentInParent = (Component)(object)hovered.GetComponentInParent<Beehive>();
				}
				if (componentInParent == null)
				{
					componentInParent = (Component)(object)hovered.GetComponentInParent<SapCollector>();
				}
			}
			if (!((Object)(object)componentInParent == (Object)null))
			{
				return componentInParent.GetComponentInParent<ZNetView>();
			}
			return null;
		}

		private static Container? HoveredChest(GameObject hovered)
		{
			Container componentInParent = hovered.GetComponentInParent<Container>();
			if (!((Object)(object)componentInParent != (Object)null) || !Chests.Contains(componentInParent) || !((Component)componentInParent).GetComponent<Piece>().IsPlacedByPlayer())
			{
				return null;
			}
			return componentInParent;
		}

		private static bool CanOpen(Player player, Container chest)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (!CraftFromContainers.HasAccess(chest, player.GetPlayerID()))
			{
				((Character)player).Message((MessageType)2, "$msg_cantopen", 0, (Sprite)null, false);
				return false;
			}
			if (!chest.m_checkGuardStone || PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, true, false))
			{
				return true;
			}
			((Character)player).Message((MessageType)2, "$msg_privatezone", 0, (Sprite)null, false);
			return false;
		}

		private static string Kind(Direction direction)
		{
			if (direction != Direction.Input)
			{
				return "output";
			}
			return "input";
		}

		private static void HandleHammerClick(Player player, Direction direction)
		{
			//IL_004a: 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)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			Piece hoveringPiece = player.GetHoveringPiece();
			if ((Object)(object)hoveringPiece == (Object)null)
			{
				((Character)player).Message((MessageType)2, "Aim at a processing piece or chest within reach", 0, (Sprite)null, false);
				return;
			}
			GameObject gameObject = ((Component)hoveringPiece).gameObject;
			Container val = HoveredChest(gameObject);
			if ((Object)(object)val != (Object)null)
			{
				if (!CanOpen(player, val))
				{
					return;
				}
				Vector3 position = ((Component)val).transform.position;
				if ((Object)(object)_selectedPiece == (Object)null || !_selectedPiece.IsValid())
				{
					UnlinkFromEveryPiece(player, position, direction);
					return;
				}
				ZNetView selectedPiece = _selectedPiece;
				_selectedPiece = null;
				if (Vector3.Distance(((Component)selectedPiece).transform.position, position) > _maxLinkDistance.Value)
				{
					((Character)player).Message((MessageType)2, $"Too far, max {_maxLinkDistance.Value:0} m", 0, (Sprite)null, false);
					return;
				}
				bool flag = RemoveChest(ReadChestPositions(selectedPiece.GetZDO(), direction) ?? new List<Vector3>(), position);
				SendLinkEdit(selectedPiece, direction, !flag, position);
				((Character)player).Message((MessageType)2, flag ? ("Unlinked " + Kind(direction) + " chest") : ("Linked " + Kind(direction) + " chest"), 0, (Sprite)null, false);
			}
			else
			{
				ZNetView val2 = LinkablePiece(gameObject, direction);
				if ((Object)(object)val2 == (Object)null || !val2.IsValid())
				{
					bool flag2 = direction == Direction.Input && (Object)(object)LinkablePiece(gameObject, Direction.Output) != (Object)null;
					((Character)player).Message((MessageType)2, flag2 ? "This piece takes no input" : "Not a processing piece or chest", 0, (Sprite)null, false);
				}
				else if ((Object)(object)_selectedPiece == (Object)(object)val2)
				{
					_selectedPiece = null;
					((Character)player).Message((MessageType)2, "Selection cleared", 0, (Sprite)null, false);
				}
				else if (!PrivateArea.CheckAccess(((Component)val2).transform.position, 0f, true, false))
				{
					((Character)player).Message((MessageType)2, "$msg_privatezone", 0, (Sprite)null, false);
				}
				else
				{
					_selectedPiece = val2;
					((Character)player).Message((MessageType)2, "Selected, now click its " + Kind(direction) + " chest", 0, (Sprite)null, false);
				}
			}
		}

		private static void UnlinkFromEveryPiece(Player player, Vector3 chestPosition, Direction direction)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			List<Piece> list = new List<Piece>();
			Piece.GetAllPiecesInRadius(chestPosition, _maxLinkDistance.Value, list);
			foreach (Piece item in list)
			{
				ZNetView componentInParent = ((Component)item).GetComponentInParent<ZNetView>();
				if (!((Object)(object)componentInParent == (Object)null) && componentInParent.IsValid())
				{
					List<Vector3> list2 = ReadChestPositions(componentInParent.GetZDO(), direction);
					if (list2 != null && RemoveChest(list2, chestPosition))
					{
						SendLinkEdit(componentInParent, direction, add: false, chestPosition);
						num++;
					}
				}
			}
			((Character)player).Message((MessageType)2, (num == 0) ? ("No piece uses this chest as " + Kind(direction)) : $"Unlinked {num} piece(s) from this {Kind(direction)} chest", 0, (Sprite)null, false);
		}

		private static void RefundOldHammers(Player player)
		{
			Inventory inventory = ((Humanoid)player).GetInventory();
			List<ItemData> list = (from i in inventory.GetAllItems()
				where i.m_shared.m_name == "$item_autostoragehammer"
				select i).ToList();
			foreach (ItemData item in list)
			{
				((Humanoid)player).RemoveEquipAction(item);
				((Humanoid)player).UnequipItem(item, false);
				if (inventory.RemoveItem(item))
				{
					Refund(player, inventory, "Wood", 3 * item.m_stack);
					Refund(player, inventory, "SurtlingCore", item.m_stack);
				}
			}
			if (list.Count > 0)
			{
				((Character)player).Message((MessageType)2, "AutoStorage Hammer refunded, use AutoStorage Link input/output (Hammer > Misc)", 0, (Sprite)null, false);
			}
		}

		private static void Refund(Player player, Inventory inventory, string prefabName, int amount)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName);
			if (!((Object)(object)itemPrefab == (Object)null) && amount > 0)
			{
				ItemData val = itemPrefab.GetComponent<ItemDrop>().m_itemData.Clone();
				val.m_dropPrefab = itemPrefab;
				val.m_stack = amount;
				val.m_worldLevel = (byte)Game.m_worldLevel;
				val.m_pickedUp = true;
				if (!inventory.AddItem(val))
				{
					Transform transform = ((Component)player).transform;
					ItemDrop.DropItem(val, val.m_stack, transform.position + transform.forward + transform.up, transform.rotation);
				}
			}
		}

		private static void ScanLinks(Vector3 around, Direction direction)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			LinkSegments.Clear();
			NearbyPieces.Clear();
			Piece.GetAllPiecesInRadius(around, _maxLinkDistance.Value, NearbyPieces);
			foreach (Piece nearbyPiece in NearbyPieces)
			{
				ZNetView componentInParent = ((Component)nearbyPiece).GetComponentInParent<ZNetView>();
				if ((Object)(object)componentInParent == (Object)null || !componentInParent.IsValid())
				{
					continue;
				}
				List<Vector3> list = ReadChestPositions(componentInParent.GetZDO(), direction);
				if (list == null)
				{
					continue;
				}
				foreach (Vector3 item in list)
				{
					LinkSegments.Add(((Component)componentInParent).transform.position + Vector3.up * 0.5f);
					LinkSegments.Add(item + Vector3.up * 0.5f);
				}
			}
		}

		private static void DrawLine(int index, Vector3 from, Vector3 to, Color color)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected O, but got Unknown
			for (int num = Lines.Count - 1; num >= 0; num--)
			{
				if ((Object)(object)Lines[num] == (Object)null)
				{
					Lines.RemoveAt(num);
				}
			}
			while (Lines.Count <= index)
			{
				LineRenderer val = new GameObject("mh_AutoStorageLink").AddComponent<LineRenderer>();
				val.useWorldSpace = true;
				val.positionCount = 2;
				float startWidth = (val.endWidth = 0.06f);
				val.startWidth = startWidth;
				if ((Object)(object)_lineMaterial == (Object)null)
				{
					Shader val2 = Shader.Find("Sprites/Default") ?? Shader.Find("Standard");
					if ((Object)(object)val2 != (Object)null)
					{
						_lineMaterial = new Material(val2);
					}
				}
				if ((Object)(object)_lineMaterial != (Object)null)
				{
					((Renderer)val).material = _lineMaterial;
				}
				Lines.Add(val);
			}
			LineRenderer obj = Lines[index];
			((Renderer)obj).enabled = true;
			Color startColor = (obj.endColor = color);
			obj.startColor = startColor;
			obj.SetPosition(0, from);
			obj.SetPosition(1, to);
		}

		private static void HideLines(int fromIndex)
		{
			for (int i = fromIndex; i < Lines.Count; i++)
			{
				if ((Object)(object)Lines[i] != (Object)null)
				{
					((Renderer)Lines[i]).enabled = false;
				}
			}
		}
	}
	internal static class BiggerChests
	{
		[HarmonyPatch(typeof(Container), "Awake")]
		private static class AddRowsOnCreate
		{
			private static void Postfix(Container __instance)
			{
				AddRows(__instance);
			}
		}

		[HarmonyPatch(typeof(Container), "UpdateRows")]
		private static class AddRowsOnLoad
		{
			private static void Postfix(Container __instance)
			{
				AddRows(__instance);
			}
		}

		private static ConfigEntry<bool> _enabled = null;

		private static ConfigEntry<int> _rowMultiplier = null;

		private static ConfigEntry<string> _prefabs = null;

		private static HashSet<string> _prefabNames = null;

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

		private static readonly Action<Container> UpdateRows = AccessTools.MethodDelegate<Action<Container>>(AccessTools.Method(typeof(Container), "UpdateRows", (Type[])null, (Type[])null), (object)null, true);

		public static void Bind(ConfigFile config)
		{
			_enabled = config.Bind<bool>("Inventory", "BiggerChests", true, Plugin.Synced("Chests listed in BiggerChestsPrefabs get more rows: their vanilla row count times BiggerChestsRowMultiplier. Turning it off keeps every row that still holds items."));
			_rowMultiplier = config.Bind<int>("Inventory", "BiggerChestsRowMultiplier", 2, Plugin.Synced("Rows of a listed chest = vanilla rows x this. 2 makes a reinforced chest 6x8 instead of 6x4.", (AcceptableValueBase?)(object)new AcceptableValueRange<int>(1, 5)));
			_prefabs = config.Bind<string>("Inventory", "BiggerChestsPrefabs", "piece_chest_wood,piece_chest,piece_chest_blackmetal,piece_chest_grausten,piece_chest_barrel,piece_chest_private", Plugin.Synced("Comma-separated prefab names of the containers that get more rows. Default: chest, reinforced chest, black metal chest, grausten chest, barrel, personal chest. Other containers work too, for example piece_chest_warderobe, Cart or VikingShip."));
			ParsePrefabs();
			_enabled.SettingChanged += delegate
			{
				Refresh();
			};
			_rowMultiplier.SettingChanged += delegate
			{
				Refresh();
			};
			_prefabs.SettingChanged += delegate
			{
				ParsePrefabs();
				Refresh();
			};
		}

		private static void ParsePrefabs()
		{
			_prefabNames = new HashSet<string>(from name in _prefabs.Value.Split(new char[1] { ',' })
				select name.Trim() into name
				where name.Length > 0
				select name, StringComparer.OrdinalIgnoreCase);
		}

		private static void Refresh()
		{
			Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
			foreach (Container val in array)
			{
				if (val.GetInventory() != null)
				{
					UpdateRows(val);
				}
			}
			if ((Object)(object)InventoryGui.instance != (Object)null && InventoryGui.instance.IsContainerOpen())
			{
				InventoryGui.instance.ContainerGrid.ResetView();
			}
		}

		private static void AddRows(Container container)
		{
			Inventory inventory = container.GetInventory();
			if (!_enabled.Value || inventory == null)
			{
				return;
			}
			ZNetView val = ContainerView.Invoke(container);
			if (!((Object)(object)val == (Object)null) && _prefabNames.Contains(Utils.GetPrefabName(((Object)((Component)val).gameObject).name)))
			{
				int num = container.m_height * _rowMultiplier.Value;
				if (inventory.GetHeight() < num)
				{
					inventory.SetHeight(num);
				}
			}
		}
	}
	internal static class BreedingLimit
	{
		[HarmonyPatch(typeof(Procreation), "Procreate")]
		private static class Patch
		{
			private static void Prefix(Procreation __instance, out int __state)
			{
				__state = __instance.m_maxCreatures;
				if (_enabled.Value)
				{
					__instance.m_maxCreatures = _maxCreatures.Value;
				}
			}

			private static void Finalizer(Procreation __instance, int __state)
			{
				__instance.m_maxCreatures = __state;
			}
		}

		private static ConfigEntry<bool> _enabled;

		private static ConfigEntry<int> _maxCreatures;

		public static void Bind(ConfigFile config)
		{
			_enabled = config.Bind<bool>("World", "BreedingLimit", true, Plugin.Synced("Tamed animals keep breeding until BreedingLimitCount animals of their kind (adults and young) stand within 10 m, instead of the handful vanilla allows."));
			_maxCreatures = config.Bind<int>("World", "BreedingLimitCount", 20, Plugin.Synced("How many animals of one kind, adults and young together, may stand within 10 m before tamed animals of that kind stop breeding. Vanilla allows only a handful, set per animal.", (AcceptableValueBase?)(object)new AcceptableValueRange<int>(1, 100)));
		}
	}
	[HarmonyPatch(typeof(Piece), "GetAllComfortPiecesInRadius")]
	internal static class ComfortRange
	{
		private static ConfigEntry<bool> _enabled;

		private static ConfigEntry<float> _rangeMeters;

		public static void Bind(ConfigFile config)
		{
			_enabled = config.Bind<bool>("Building", "ComfortRange", true, Plugin.Synced("Beds, chairs, rugs, fires and other comfort pieces count towards your comfort level from up to ComfortRangeMeters away instead of the vanilla 10 m. Resting still starts only near a burning fire, sitting or under a roof; the longer range only raises the comfort level, which sets how long Rested lasts."));
			_rangeMeters = config.Bind<float>("Building", "ComfortRangeMeters", 20f, Plugin.Synced("How far from you, in meters, a comfort piece still counts. Vanilla is 10.", (AcceptableValueBase?)(object)new AcceptableValueRange<float>(1f, 64f)));
		}

		private static void Prefix(ref float radius)
		{
			if (_enabled.Value)
			{
				radius = _rangeMeters.Value;
			}
		}
	}
	internal static class CraftFromContainers
	{
		[HarmonyPatch(typeof(Container), "Awake")]
		private static class RegisterChest
		{
			private static void Postfix(Container __instance)
			{
				if (__instance.GetInventory() != null)
				{
					ZNetView val = ContainerView.Invoke(__instance);
					if (!((Object)(object)val == (Object)null) && val.IsValid() && !((Object)(object)((Component)__instance).GetComponent<Piece>() == (Object)null) && !((Object)(object)__instance.m_wagon != (Object)null) && !((Object)(object)__instance.m_rootObjectOverride != (Object)null) && !__instance.m_autoDestroyEmpty && !((Object)(object)((Component)__instance).GetComponentInParent<Ship>() != (Object)null) && !((Object)(object)((Component)__instance).GetComponentInParent<Incinerator>() != (Object)null) && !((Object)__instance).name.StartsWith("FeedingTrough", StringComparison.Ordinal))
					{
						Chests.Add(__instance);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "CountItems")]
		private static class AddChestCounts
		{
			private static void Postfix(Inventory __instance, string name, int quality, bool matchWorldLevel, ref int __result)
			{
				if (_countScope == 0 || name == null)
				{
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if (!Active(localPlayer) || __instance != ((Humanoid)localPlayer).GetInventory())
				{
					return;
				}
				__result += CountHeld(name, quality, matchWorldLevel);
				bool countNotOwned = _uiDepth > 0;
				foreach (Container item in ChestsInRange())
				{
					if (Countable(item, countNotOwned))
					{
						__result += LikedItems.CountAvailable(LoadedInventory(item), name, quality, matchWorldLevel);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Player), "HaveRequirementItems")]
		private static class CountForRecipe
		{
			private static void Prefix(Recipe piece, out bool __state)
			{
				__state = !piece.m_requireOnlyOneIngredient;
				if (__state)
				{
					_countScope++;
				}
			}

			private static void Finalizer(bool __state)
			{
				if (__state)
				{
					_countScope--;
				}
			}
		}

		[HarmonyPatch(typeof(Player), "HaveRequirements", new Type[]
		{
			typeof(Piece),
			typeof(RequirementMode)
		})]
		private static class CountForPiece
		{
			private static void Prefix()
			{
				_countScope++;
			}

			private static void Finalizer()
			{
				_countScope--;
			}
		}

		[HarmonyPatch]
		private static class CountChestsNotOwnedInUi
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				yield return AccessTools.Method(typeof(InventoryGui), "UpdateRecipe", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(Hud), "UpdateBuild", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(BuildUiPieceButton), "UpdateRequirements", (Type[])null, (Type[])null);
			}

			private static void Prefix()
			{
				_uiDepth++;
			}

			private static void Finalizer()
			{
				_uiDepth--;
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "SetupRequirementList")]
		private static class NoChestCountsForOneIngredientRecipe
		{
			private static void Prefix(InventoryGui __instance)
			{
				object? obj = SelectedRecipeGetter.Invoke(SelectedRecipeField.GetValue(__instance), null);
				Recipe val = (Recipe)((obj is Recipe) ? obj : null);
				_drawingOneIngredientRecipe = (Object)(object)val != (Object)null && val.m_requireOnlyOneIngredient;
			}

			private static void Finalizer()
			{
				_drawingOneIngredientRecipe = false;
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "SetupRequirement")]
		private static class CountForDisplay
		{
			private static void Prefix(out bool __state)
			{
				__state = !_drawingOneIngredientRecipe;
				if (__state)
				{
					_countScope++;
				}
			}

			private static void Postfix(Transform elementRoot, Requirement req, Player player, int quality, int craftMultiplier, bool __result, bool __state)
			{
				if (__state && __result && Active(player) && Object.op_Implicit((Object)(object)req.m_resItem))
				{
					int num = ((Humanoid)player).GetInventory().CountItems(req.m_resItem.m_itemData.m_shared.m_name, -1, true);
					int num2 = req.GetAmount(quality) * craftMultiplier;
					((Component)elementRoot.Find("res_amount")).GetComponent<TMP_Text>().text = $"{num}/{num2}";
				}
			}

			private static void Finalizer(bool __state)
			{
				if (__state)
				{
					_countScope--;
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
		private static class CraftFromOwnedChestsOnly
		{
			private static void Prefix(out int __state)
			{
				__state = BeginOwnedOnly("Chest busy, try again");
			}

			private static void Finalizer(int __state)
			{
				EndOwnedOnly(__state);
				ReturnHeld();
			}
		}

		[HarmonyPatch(typeof(Player), "UpdatePlacement")]
		private static class PlaceFromOwnedChestsOnly
		{
			private static void Prefix(Player __instance, out int __state)
			{
				RequestChestsForSelectedPiece(__instance);
				__state = BeginOwnedOnly("Fetching materials, click again");
			}

			private static void Finalizer(int __state)
			{
				EndOwnedOnly(__state);
			}
		}

		[HarmonyPatch(typeof(Player), "HaveRequirements", new Type[]
		{
			typeof(Recipe),
			typeof(bool),
			typeof(int),
			typeof(int)
		})]
		private static class ExplainBusyChestOnCraft
		{
			private static void Postfix(Player __instance, Recipe recipe, bool discover, int qualityLevel, int amount, bool __result)
			{
				if (!(__result || discover) && InOwnedOnlyCheck(__instance) && !ZoneSystem.instance.GetGlobalKey((GlobalKeys)25))
				{
					_showShortMessage = PassesCountingChestsNotOwned(() => __instance.HaveRequirements(recipe, discover, qualityLevel, amount));
				}
			}
		}

		[HarmonyPatch(typeof(Player), "HaveRequirements", new Type[]
		{
			typeof(Piece),
			typeof(RequirementMode)
		})]
		private static class FetchChestsOnShortPlaceClick
		{
			private static void Postfix(Player __instance, Piece piece, RequirementMode mode, bool __result)
			{
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				if (!__result && (int)mode == 0 && InOwnedOnlyCheck(__instance) && PassesCountingChestsNotOwned(() => __instance.HaveRequirements(piece, mode)))
				{
					_showShortMessage = true;
					RequestChestsHolding(piece.m_resources);
				}
			}
		}

		[HarmonyPatch(typeof(Container), "RPC_OpenResponse")]
		private static class HideResponseToOurRequest
		{
			private static bool Prefix(Container __instance, bool granted)
			{
				OpenedAt.Remove(__instance);
				if (!RequestedByUs.Remove(__instance))
				{
					return true;
				}
				if (!granted)
				{
					Refused.Add(__instance);
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(Container), "Interact")]
		private static class OpenOnRealInteract
		{
			private static void Prefix(Container __instance, bool hold)
			{
				RequestedByUs.Remove(__instance);
				if (!hold)
				{
					OpenedAt[__instance] = Time.time;
				}
			}
		}

		private sealed class HeldItem
		{
			public readonly Container Chest;

			public readonly ItemData Item;

			public HeldItem(Container chest, ItemData item)
			{
				Chest = chest;
				Item = item;
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")]
		private static class HoldOnCraftClick
		{
			private static void Postfix(InventoryGui __instance)
			{
				Recipe val = CraftRecipe.Invoke(__instance);
				Player localPlayer = Player.m_localPlayer;
				if (CraftTimer.Invoke(__instance) < 0f || (Object)(object)val == (Object)null || val.m_requireOnlyOneIngredient || !Active(localPlayer))
				{
					return;
				}
				ReturnHeld();
				if (!ZoneSystem.instance.GetGlobalKey((GlobalKeys)25))
				{
					ItemData val2 = CraftUpgradeItem.Invoke(__instance);
					int qualityLevel = ((val2 == null) ? 1 : (val2.m_quality + 1));
					int multiplier = ((!MultiCrafting.Invoke(__instance)) ? 1 : __instance.m_multiCraftAmount);
					_stillMissing = Missing(localPlayer, val.m_resources, qualityLevel, -1, multiplier) ?? new Dictionary<string, int>();
					HoldFromOwnedChests();
					if (_stillMissing.Count > 0)
					{
						RequestChestsHolding(Array.FindAll(val.m_resources, StillMissing));
					}
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "Update")]
		private static class FollowHeldCraft
		{
			private static void Postfix(InventoryGui __instance)
			{
				if (_stillMissing == null && Held.Count == 0)
				{
					return;
				}
				if (CraftTimer.Invoke(__instance) < 0f)
				{
					ReturnHeld();
					return;
				}
				Dictionary<string, int> stillMissing = _stillMissing;
				if (stillMissing != null && stillMissing.Count > 0 && Active(Player.m_localPlayer))
				{
					HoldFromOwnedChests();
				}
			}
		}

		[HarmonyPatch(typeof(Game), "Shutdown")]
		private static class ReturnHeldOnShutdown
		{
			private static void Prefix()
			{
				ReturnHeld();
			}
		}

		[HarmonyPatch(typeof(Player), "ConsumeResources")]
		private static class TakeFromChests
		{
			private static void Prefix(Player __instance, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier, out Dictionary<string, int>? __state)
			{
				__state = (Active(__instance) ? Missing(__instance, requirements, qualityLevel, itemQuality, multiplier) : null);
			}

			private static void Postfix(int itemQuality, Dictionary<string, int>? __state)
			{
				if (__state == null)
				{
					return;
				}
				foreach (KeyValuePair<string, int> item in __state)
				{
					string key = item.Key;
					int num = TakeHeld(key, itemQuality, item.Value);
					foreach (Container item2 in ChestsInRange())
					{
						if (num <= 0)
						{
							break;
						}
						if (IsOwned(item2))
						{
							Inventory inventory = LoadedInventory(item2);
							int num2 = Mathf.Min(num, LikedItems.CountAvailable(inventory, key, itemQuality));
							if (num2 > 0)
							{
								LikedItems.RemoveAvailable(inventory, key, num2, itemQuality);
								num -= num2;
							}
						}
					}
					if (num > 0)
					{
						Plugin.Log.LogWarning((object)$"CraftFromContainers: {num} x {key} could not be taken from nearby chests");
					}
				}
			}
		}

		private const string ChestBusyMessage = "Chest busy, try again";

		private const string FetchingMessage = "Fetching materials, click again";

		private static ConfigEntry<bool> _enabled = null;

		private static ConfigEntry<float> _radius = null;

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

		private static readonly Func<Container, bool> ContainerLoad = AccessTools.MethodDelegate<Func<Container, bool>>(AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null), (object)null, true);

		private static readonly FieldRef<InventoryGui, float> CraftTimer = AccessTools.FieldRefAccess<InventoryGui, float>("m_craftTimer");

		private static readonly FieldRef<InventoryGui, Recipe> CraftRecipe = AccessTools.FieldRefAccess<InventoryGui, Recipe>("m_craftRecipe");

		private static readonly FieldRef<InventoryGui, ItemData?> CraftUpgradeItem = AccessTools.FieldRefAccess<InventoryGui, ItemData>("m_craftUpgradeItem");

		private static readonly FieldRef<InventoryGui, bool> MultiCrafting = AccessTools.FieldRefAccess<InventoryGui, bool>("m_multiCrafting");

		private static readonly FieldInfo SelectedRecipeField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe");

		private static readonly MethodInfo SelectedRecipeGetter = AccessTools.PropertyGetter(SelectedRecipeField.FieldType, "Recipe");

		private static readonly List<Container> Chests = new List<Container>();

		private static readonly List<Container> InRange = new List<Container>();

		private static int _inRangeFrame = -1;

		private static readonly Action<Container> UpdateUseVisual = AccessTools.MethodDelegate<Action<Container>>(AccessTools.Method(typeof(Container), "UpdateUseVisual", (Type[])null, (Type[])null), (object)null, true);

		private static int _countScope;

		private static int _uiDepth;

		private static bool _drawingOneIngredientRecipe;

		private static string? _shortMessage;

		private static bool _showShortMessage;

		private static readonly HashSet<Container> RequestedByUs = new HashSet<Container>();

		private static Piece? _requestedForPiece;

		private static readonly HashSet<Container> Refused = new HashSet<Container>();

		private static readonly Dictionary<Container, float> OpenedAt = new Dictionary<Container, float>();

		private const float OpenAnswerTimeout = 3f;

		private static readonly List<HeldItem> Held = new List<HeldItem>();

		private static Dictionary<string, int>? _stillMissing;

		public static void Bind(ConfigFile config)
		{
			_enabled = config.Bind<bool>("Crafting", "CraftFromContainers", true, Plugin.Synced("Crafting, upgrading and building also use materials from chests near you, after your own inventory. A craft takes its materials out of the chests when you click Craft and puts back what is left if you cancel. In multiplayer a chest another player has open is skipped until they close it; you get a short message and can try again."));
			_radius = config.Bind<float>("Crafting", "CraftFromContainersRadius", 15f, Plugin.Synced("How far from you, in meters, a chest can be to be used.", (AcceptableValueBase?)(object)new AcceptableValueRange<float>(1f, 50f)));
		}

		internal static bool HasAccess(Container chest, long playerID)
		{
			//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_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			PrivacySetting privacy = chest.m_privacy;
			if ((int)privacy != 0)
			{
				if ((int)privacy == 2)
				{
					return true;
				}
				return false;
			}
			return ((Component)chest).GetComponent<Piece>().GetCreator() == playerID;
		}

		private static List<Container> ChestsInRange()
		{
			if (_inRangeFrame == Time.frameCount)
			{
				return InRange;
			}
			_inRangeFrame = Time.frameCount;
			InRange.Clear();
			UsableChestsWithin(_radius.Value, InRange);
			return InRange;
		}

		internal static void UsableChestsWithin(float radius, List<Container> result)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			long playerID = localPlayer.GetPlayerID();
			Vector3 position = ((Component)localPlayer).transform.position;
			for (int num = Chests.Count - 1; num >= 0; num--)
			{
				Container val = Chests[num];
				if ((Object)(object)val == (Object)null)
				{
					Chests.RemoveAt(num);
					RequestedByUs.Remove(val);
					OpenedAt.Remove(val);
					Refused.Remove(val);
				}
				else if (ContainerView.Invoke(val).IsValid() && !(Vector3.Distance(((Component)val).transform.position, position) > radius) && ((Component)val).GetComponent<Piece>().IsPlacedByPlayer() && HasAccess(val, playerID) && (!val.m_checkGuardStone || PrivateArea.CheckAccess(((Component)val).transform.position, 0f, false, false)))
				{
					result.Add(val);
				}
			}
		}

		internal static bool IsOwned(Container chest)
		{
			ZNetView val = ContainerView.Invoke(chest);
			if ((Object)(object)val != (Object)null && val.IsValid())
			{
				return val.IsOwner();
			}
			return false;
		}

		private static bool OpenElsewhere(ZNetView view)
		{
			return view.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 1;
		}

		internal static bool Busy(Container chest)
		{
			ZNetView val = ContainerView.Invoke(chest);
			if (!((Object)(object)val == (Object)null) && val.IsValid())
			{
				if (!val.IsOwner())
				{
					return Refused.Contains(chest);
				}
				return false;
			}
			return true;
		}

		private static bool Countable(Container chest, bool countNotOwned)
		{
			ZNetView val = ContainerView.Invoke(chest);
			if (val.IsValid())
			{
				if (!val.IsOwner())
				{
					if (countNotOwned)
					{
						return !OpenElsewhere(val);
					}
					return false;
				}
				return true;
			}
			return false;
		}

		internal static Inventory LoadedInventory(Container chest)
		{
			LoadChest(chest);
			return chest.GetInventory();
		}

		internal static void LoadChest(Container chest)
		{
			if (ContainerLoad(chest))
			{
				UpdateUseVisual(chest);
			}
		}

		private static bool HoldsAny(Inventory inventory, Requirement[] requirements)
		{
			foreach (Requirement val in requirements)
			{
				if (Object.op_Implicit((Object)(object)val.m_resItem) && LikedItems.CountAvailable(inventory, val.m_resItem.m_itemData.m_shared.m_name) > 0)
				{
					return true;
				}
			}
			return false;
		}

		private static bool Active(Player player)
		{
			if (_enabled.Value && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer)
			{
				return !player.NoCostCheat();
			}
			return false;
		}

		private static int BeginOwnedOnly(string shortMessage)
		{
			int uiDepth = _uiDepth;
			_uiDepth = 0;
			_shortMessage = shortMessage;
			_showShortMessage = false;
			return uiDepth;
		}

		private static void EndOwnedOnly(int uiDepth)
		{
			Player localPlayer = Player.m_localPlayer;
			if (_showShortMessage && _shortMessage != null && (Object)(object)localPlayer != (Object)null)
			{
				((Character)localPlayer).Message((MessageType)2, _shortMessage, 0, (Sprite)null, false);
			}
			_showShortMessage = false;
			_shortMessage = null;
			_uiDepth = uiDepth;
		}

		private static bool InOwnedOnlyCheck(Player player)
		{
			if (_shortMessage != null && _uiDepth == 0)
			{
				return Active(player);
			}
			return false;
		}

		private static bool PassesCountingChestsNotOwned(Func<bool> check)
		{
			_uiDepth++;
			try
			{
				return check();
			}
			finally
			{
				_uiDepth--;
			}
		}

		private static void RequestChestsHolding(Requirement[] requirements)
		{
			if (!Active(Player.m_localPlayer))
			{
				return;
			}
			foreach (Container item in ChestsInRange())
			{
				ZNetView val = ContainerView.Invoke(item);
				if (!((Object)(object)val == (Object)null) && val.IsValid() && !val.IsOwner() && HoldsAny(LoadedInventory(item), requirements))
				{
					RequestChest(item);
				}
			}
		}

		internal static bool RequestChest(Container chest)
		{
			Player localPlayer = Player.m_localPlayer;
			ZNetView val = ContainerView.Invoke(chest);
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)val == (Object)null || !val.IsValid())
			{
				return false;
			}
			if (val.IsOwner())
			{
				return true;
			}
			if (OpenedAt.TryGetValue(chest, out var value) && Time.time - value < 3f)
			{
				return true;
			}
			if (val.GetZDO().GetOwner() == 0L)
			{
				return false;
			}
			Refused.Remove(chest);
			RequestedByUs.Add(chest);
			val.InvokeRPC("RPC_RequestOpen", new object[1] { localPlayer.GetPlayerID() });
			return true;
		}

		private static void RequestChestsForSelectedPiece(Player player)
		{
			if ((Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			Piece val = ((Active(player) && ((Character)player).InPlaceMode()) ? player.GetSelectedPiece() : null);
			if (!((Object)(object)val == (Object)(object)_requestedForPiece))
			{
				_requestedForPiece = val;
				if ((Object)(object)val != (Object)null)
				{
					RequestChestsHolding(val.m_resources);
				}
			}
		}

		private static Dictionary<string, int>? Missing(Player player, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier)
		{
			Dictionary<string, int> dictionary = null;
			CraftingStation currentCraftingStation = player.GetCurrentCraftingStation();
			Inventory inventory = ((Humanoid)player).GetInventory();
			foreach (Requirement val in requirements)
			{
				if (((Object)(object)currentCraftingStation != (Object)null && currentCraftingStation.m_upgrader != val.m_upgraderResource) || ((Object)(object)currentCraftingStation == (Object)null && val.m_upgraderResource) || !Object.op_Implicit((Object)(object