Decompiled source of Nearby Chests v1.2.1

plugins/NearbyChests.dll

Decompiled 3 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace NearbyChests
{
	internal static class BuildPull
	{
		public static bool ShortcutPressed()
		{
			//IL_0005: 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_000d: 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_0035: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = Plugin.PullBuildMaterialsKey.Value;
			if ((int)((KeyboardShortcut)(ref value)).MainKey == 0 || !Plugin.ShortcutKeyPressed(((KeyboardShortcut)(ref value)).MainKey, down: true))
			{
				return false;
			}
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref value)).Modifiers)
			{
				if (!Plugin.ShortcutKeyPressed(modifier, down: false))
				{
					return false;
				}
			}
			return true;
		}

		public static void Pull(Player player)
		{
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			Piece selectedPiece = player.GetSelectedPiece();
			if ((Object)(object)selectedPiece == (Object)null)
			{
				((Character)player).Message((MessageType)2, "Select a piece to build first", 0, (Sprite)null, false);
				return;
			}
			string text = Localization.instance.Localize(selectedPiece.m_name);
			List<Requirement> list = selectedPiece.m_resources.Where((Requirement r) => (Object)(object)r.m_resItem != (Object)null && r.m_amount > 0).ToList();
			if (list.Count == 0)
			{
				((Character)player).Message((MessageType)2, text + " costs nothing", 0, (Sprite)null, false);
				return;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			int num = int.MaxValue;
			foreach (Requirement item in list)
			{
				num = Mathf.Min(num, Carried(inventory, item) / item.m_amount);
			}
			int num2 = num + Mathf.Max(1, Plugin.PullBuildSets.Value);
			ChestFinder.Invalidate();
			List<Container> nearby = ChestFinder.GetNearby(player, Plugin.CraftingRange.Value);
			HashSet<Container> hashSet = new HashSet<Container>();
			List<string> list2 = new List<string>();
			List<string> list3 = new List<string>();
			bool flag = false;
			foreach (Requirement item2 in list)
			{
				string name = item2.m_resItem.m_itemData.m_shared.m_name;
				int num3 = item2.m_amount * num2 - Carried(inventory, item2);
				if (num3 > 0)
				{
					bool inventoryFull;
					int num4 = Take(inventory, nearby, name, num3, hashSet, out inventoryFull);
					flag = flag || inventoryFull;
					string arg = Localization.instance.Localize(name);
					if (num4 > 0)
					{
						list2.Add($"{num4} {arg}");
					}
					if (num4 < num3 && !inventoryFull)
					{
						list3.Add($"{num3 - num4} {arg}");
					}
				}
			}
			if (hashSet.Count > 0)
			{
				inventory.Changed(false, false);
				ChestFinder.InvalidateCounts();
				foreach (Container item3 in hashSet)
				{
					InventoryGui.instance.m_moveItemEffects.Create(((Component)item3).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
				}
			}
			List<string> list4 = new List<string>();
			if (list2.Count > 0)
			{
				list4.Add("Pulled " + string.Join(", ", list2.ToArray()) + " for " + text);
			}
			if (flag)
			{
				list4.Add("Inventory full");
			}
			if (list3.Count > 0)
			{
				list4.Add("Nearby chests are short " + string.Join(", ", list3.ToArray()));
			}
			if (list4.Count == 0)
			{
				list4.Add("Already carrying enough for " + text);
			}
			((Character)player).Message((MessageType)2, string.Join("\n", list4.ToArray()), 0, (Sprite)null, false);
		}

		private static int Carried(Inventory inv, Requirement r)
		{
			return inv.CountItems(r.m_resItem.m_itemData.m_shared.m_name, -1, true);
		}

		private static int Take(Inventory inv, List<Container> chests, string name, int amount, HashSet<Container> touched, out bool inventoryFull)
		{
			inventoryFull = false;
			int num = 0;
			foreach (Container chest in chests)
			{
				if (amount <= 0)
				{
					break;
				}
				Inventory inventory = chest.GetInventory();
				if (!inventory.HaveItem(name, false) || !ChestFinder.EnsureOwner(chest))
				{
					continue;
				}
				foreach (ItemData item in inventory.GetAllItems().ToList())
				{
					if (amount > 0 && !(item.m_shared.m_name != name))
					{
						if (!inv.HaveEmptySlot() && inv.FindFreeStackSpace(name, (float)item.m_worldLevel) <= 0)
						{
							inventoryFull = true;
							return num;
						}
						int num2 = Mathf.Min(amount, item.m_stack);
						ItemData val = item.Clone();
						val.m_stack = num2;
						int num3 = (inv.AddItem(val) ? num2 : (num2 - val.m_stack));
						if (num3 <= 0)
						{
							inventoryFull = true;
							return num;
						}
						inventory.RemoveItem(item, num3);
						touched.Add(chest);
						num += num3;
						amount -= num3;
					}
				}
			}
			return num;
		}
	}
	[HarmonyPatch(typeof(Player), "UpdatePlacement")]
	internal static class Player_UpdatePlacement_BuildPull_Patch
	{
		private static void Prefix(Player __instance, ref bool takeInput)
		{
			if (takeInput && Plugin.PullBuildMaterials.Value && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && ((Character)__instance).InPlaceMode() && !((Character)__instance).IsDead() && !Hud.IsPieceSelectionVisible() && BuildPull.ShortcutPressed())
			{
				BuildPull.Pull(__instance);
				takeInput = false;
			}
		}
	}
	internal static class ChestFinder
	{
		private const float CacheSeconds = 1f;

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

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

		private static readonly Dictionary<string, int> CountCache = new Dictionary<string, int>();

		private static float _cachedAt = -1000f;

		private static Vector3 _cachedPos;

		private static float _cachedRange = -1f;

		public static void Register(Container container)
		{
			Known.Add(container);
		}

		internal static bool CanUseOpened(Container c, Player player)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.OnlyUseMarkedChests.Value)
			{
				return true;
			}
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			if (IsUsable(c, ((Component)player).transform.position, Plugin.StackingRange.Value * Plugin.StackingRange.Value, Game.instance.GetPlayerProfile().GetPlayerID()))
			{
				return true;
			}
			((Character)player).Message((MessageType)2, (!ChestSelection.Allows(c)) ? "This chest is not marked. Tick Include in NearbyChests to use Stack or Tidy." : "This chest is not eligible for NearbyChests. Check its range, access and storage settings.", 0, (Sprite)null, false);
			return false;
		}

		internal static bool IsUsable(Container c, Player player, float range)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player != (Object)null)
			{
				return IsUsable(c, ((Component)player).transform.position, range * range, Game.instance.GetPlayerProfile().GetPlayerID());
			}
			return false;
		}

		public static void Invalidate()
		{
			_cachedAt = -1000f;
			CountCache.Clear();
		}

		public static void InvalidateCounts()
		{
			CountCache.Clear();
		}

		public static List<Container> GetNearby(Player player, float range)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			Vector3 pos = ((Component)player).transform.position;
			if (Time.time - _cachedAt < 1f && range == _cachedRange)
			{
				Vector3 val = pos - _cachedPos;
				if (((Vector3)(ref val)).sqrMagnitude < 1f)
				{
					return Nearby;
				}
			}
			_cachedAt = Time.time;
			_cachedPos = pos;
			_cachedRange = range;
			CountCache.Clear();
			Nearby.Clear();
			Known.RemoveWhere((Container c) => (Object)(object)c == (Object)null);
			float rangeSq = range * range;
			long playerID = Game.instance.GetPlayerProfile().GetPlayerID();
			foreach (Container item in Known)
			{
				if (IsUsable(item, pos, rangeSq, playerID))
				{
					Nearby.Add(item);
				}
			}
			Nearby.Sort(delegate(Container a, Container b)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_0027: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0032: Unknown result type (might be due to invalid IL or missing references)
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val2 = ((Component)a).transform.position - pos;
				float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude;
				val2 = ((Component)b).transform.position - pos;
				return sqrMagnitude.CompareTo(((Vector3)(ref val2)).sqrMagnitude);
			});
			return Nearby;
		}

		private static bool IsUsable(Container c, Vector3 playerPos, float rangeSq, long playerId)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)c == (Object)null || (Object)(object)c.m_nview == (Object)null || !c.m_nview.IsValid() || c.GetInventory() == null)
			{
				return false;
			}
			if (!ChestSelection.Allows(c))
			{
				return false;
			}
			Vector3 val = ((Component)c).transform.position - playerPos;
			if (((Vector3)(ref val)).sqrMagnitude > rangeSq)
			{
				return false;
			}
			if (IsDisposal(c))
			{
				return false;
			}
			if ((Object)(object)c.m_wagon != (Object)null || (Object)(object)((Component)c).GetComponentInParent<Ship>() != (Object)null)
			{
				if (!Plugin.IncludeCartsAndShips.Value)
				{
					return false;
				}
			}
			else if ((Object)(object)c.m_piece == (Object)null || !c.m_piece.IsPlacedByPlayer())
			{
				return false;
			}
			if ((int)c.m_privacy == 0 && (Object)(object)c.m_piece == (Object)null)
			{
				return false;
			}
			if (!c.CheckAccess(playerId))
			{
				return false;
			}
			if (c.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)c).transform.position, 0f, false, false))
			{
				return false;
			}
			if (!c.m_nview.IsOwner() && c.m_nview.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 1)
			{
				return false;
			}
			return true;
		}

		public static bool IsDisposal(Container c)
		{
			if ((Object)(object)c != (Object)null)
			{
				if (!((Object)(object)((Component)c).GetComponentInParent<Incinerator>() != (Object)null))
				{
					return (Object)(object)((Component)c).GetComponentInParent<TombStone>() != (Object)null;
				}
				return true;
			}
			return false;
		}

		public static bool EnsureOwner(Container c)
		{
			if ((Object)(object)c == (Object)null || (Object)(object)c.m_nview == (Object)null || !c.m_nview.IsValid() || !ChestSelection.Allows(c))
			{
				return false;
			}
			if (c.m_nview.IsOwner())
			{
				return true;
			}
			if (c.m_nview.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 1)
			{
				return false;
			}
			c.m_nview.ClaimOwnership();
			return c.m_nview.IsOwner();
		}

		public static int Count(Player player, string name, int quality, bool matchWorldLevel)
		{
			List<Container> nearby = GetNearby(player, Plugin.CraftingRange.Value);
			string key = name + "|" + quality + "|" + matchWorldLevel;
			if (CountCache.TryGetValue(key, out var value))
			{
				return value;
			}
			int num = 0;
			foreach (Container item in nearby)
			{
				num += item.GetInventory().CountItems(name, quality, matchWorldLevel);
			}
			CountCache[key] = num;
			return num;
		}

		public static bool Have(Player player, string name, bool matchWorldLevel)
		{
			foreach (Container item in GetNearby(player, Plugin.CraftingRange.Value))
			{
				if (item.GetInventory().HaveItem(name, matchWorldLevel))
				{
					return true;
				}
			}
			return false;
		}

		public static ItemData Find(Player player, string name, int quality)
		{
			foreach (Container item2 in GetNearby(player, Plugin.CraftingRange.Value))
			{
				ItemData item = item2.GetInventory().GetItem(name, quality, false);
				if (item != null)
				{
					return item;
				}
			}
			return null;
		}

		public static void Remove(Player player, string name, int amount, int quality, bool worldLevelBased)
		{
			foreach (Container item in GetNearby(player, Plugin.CraftingRange.Value))
			{
				if (amount <= 0)
				{
					break;
				}
				Inventory inventory = item.GetInventory();
				int num = inventory.CountItems(name, quality, worldLevelBased);
				if (num > 0 && EnsureOwner(item))
				{
					int num2 = Mathf.Min(num, amount);
					inventory.RemoveItem(name, num2, quality, worldLevelBased);
					amount -= num2;
				}
			}
			CountCache.Clear();
		}
	}
	internal static class ChestSelection
	{
		internal static readonly int Key = StringExtensionMethods.GetStableHashCode("NearbyChests.Included");

		internal static bool IsMarked(Container c)
		{
			if ((Object)(object)c != (Object)null && (Object)(object)c.m_nview != (Object)null && c.m_nview.IsValid())
			{
				return c.m_nview.GetZDO().GetBool(Key, false);
			}
			return false;
		}

		internal static bool Allows(Container c)
		{
			if (Plugin.OnlyUseMarkedChests.Value)
			{
				return IsMarked(c);
			}
			return true;
		}

		internal static bool IsSupported(Container c)
		{
			if ((Object)(object)c == (Object)null || (Object)(object)c.m_nview == (Object)null || !c.m_nview.IsValid() || c.GetInventory() == null || ChestFinder.IsDisposal(c))
			{
				return false;
			}
			if ((Object)(object)c.m_wagon != (Object)null || (Object)(object)((Component)c).GetComponentInParent<Ship>() != (Object)null)
			{
				return Plugin.IncludeCartsAndShips.Value;
			}
			if ((Object)(object)c.m_piece != (Object)null)
			{
				return c.m_piece.IsPlacedByPlayer();
			}
			return false;
		}

		internal static bool CanEdit(InventoryGui gui, Container c)
		{
			//IL_005d: 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)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsTeleporting() && !((Character)localPlayer).IsDead() && (Object)(object)gui != (Object)null && (Object)(object)gui.m_currentContainer == (Object)(object)c && IsSupported(c) && c.IsInUse() && c.m_nview.IsOwner() && ((int)c.m_privacy != 0 || (Object)(object)c.m_piece != (Object)null) && c.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID()))
			{
				if (c.m_checkGuardStone)
				{
					return PrivateArea.CheckAccess(((Component)c).transform.position, 0f, false, false);
				}
				return true;
			}
			return false;
		}

		internal static bool TrySet(InventoryGui gui, Container c, bool included)
		{
			if (!CanEdit(gui, c))
			{
				return false;
			}
			c.m_nview.GetZDO().Set(Key, included);
			ChestFinder.Invalidate();
			return true;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "Awake")]
	internal static class ChestSelectionUI
	{
		private static Toggle _toggle;

		private static Container _container;

		private static void Postfix(InventoryGui __instance)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Expected O, but got Unknown
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Expected O, but got Unknown
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0237: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a5: 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_02ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0304: Unknown result type (might be due to invalid IL or missing references)
			//IL_030f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0319: Unknown result type (might be due to invalid IL or missing references)
			//IL_0324: Unknown result type (might be due to invalid IL or missing references)
			//IL_032e: Unknown result type (might be due to invalid IL or missing references)
			//IL_032f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0334: Unknown result type (might be due to invalid IL or missing references)
			//IL_0335: Unknown result type (might be due to invalid IL or missing references)
			//IL_033c: Unknown result type (might be due to invalid IL or missing references)
			//IL_034e: Unknown result type (might be due to invalid IL or missing references)
			_container = null;
			_toggle = null;
			if (!((Object)(object)__instance.m_container == (Object)null) && !((Object)(object)__instance.m_stackAllButton == (Object)null))
			{
				GameObject val = new GameObject("NearbyChests_Included", new Type[2]
				{
					typeof(RectTransform),
					typeof(Toggle)
				});
				RectTransform val2 = (RectTransform)val.transform;
				((Transform)val2).SetParent(((Component)__instance.m_container).transform, false);
				Vector2 val3 = default(Vector2);
				((Vector2)(ref val3))..ctor(0f, 0f);
				val2.anchorMax = val3;
				val2.anchorMin = val3;
				val2.pivot = new Vector2(0f, 1f);
				val2.anchoredPosition = new Vector2(12f, -8f);
				val2.sizeDelta = new Vector2(280f, 26f);
				GameObject val4 = new GameObject("Box", new Type[2]
				{
					typeof(RectTransform),
					typeof(Image)
				});
				RectTransform val5 = (RectTransform)val4.transform;
				((Transform)val5).SetParent((Transform)(object)val2, false);
				((Vector2)(ref val3))..ctor(0f, 0.5f);
				val5.anchorMax = val3;
				val5.anchorMin = val3;
				val5.pivot = new Vector2(0f, 0.5f);
				val5.sizeDelta = new Vector2(22f, 22f);
				Image component = val4.GetComponent<Image>();
				Image component2 = ((Component)__instance.m_stackAllButton).GetComponent<Image>();
				if ((Object)(object)component2 != (Object)null)
				{
					component.sprite = component2.sprite;
					component.type = component2.type;
				}
				((Graphic)component).color = new Color(0.65f, 0.65f, 0.65f);
				TMP_Text componentInChildren = ((Component)__instance.m_stackAllButton).GetComponentInChildren<TMP_Text>(true);
				GameObject val6 = new GameObject("Label", new Type[2]
				{
					typeof(RectTransform),
					typeof(TextMeshProUGUI)
				});
				RectTransform val7 = (RectTransform)val6.transform;
				((Transform)val7).SetParent((Transform)(object)val2, false);
				val7.anchorMin = Vector2.zero;
				val7.anchorMax = Vector2.one;
				val7.offsetMin = new Vector2(30f, 0f);
				val7.offsetMax = Vector2.zero;
				TextMeshProUGUI component3 = val6.GetComponent<TextMeshProUGUI>();
				if ((Object)(object)componentInChildren != (Object)null)
				{
					((TMP_Text)component3).font = componentInChildren.font;
				}
				((TMP_Text)component3).fontSize = 18f;
				((Graphic)component3).color = (Color)(((Object)(object)componentInChildren != (Object)null) ? ((Graphic)componentInChildren).color : new Color(1f, 0.85f, 0.55f));
				((TMP_Text)component3).alignment = (TextAlignmentOptions)4097;
				((TMP_Text)component3).text = "Include in NearbyChests";
				GameObject val8 = new GameObject("Check", new Type[2]
				{
					typeof(RectTransform),
					typeof(Image)
				});
				RectTransform val9 = (RectTransform)val8.transform;
				((Transform)val9).SetParent((Transform)(object)val5, false);
				val9.anchorMin = new Vector2(0.25f, 0.25f);
				val9.anchorMax = new Vector2(0.75f, 0.75f);
				val3 = (val9.offsetMin = (val9.offsetMax = Vector2.zero));
				Image component4 = val8.GetComponent<Image>();
				((Graphic)component4).color = ((Graphic)component3).color;
				((Graphic)component4).raycastTarget = false;
				_toggle = val.GetComponent<Toggle>();
				((Selectable)_toggle).targetGraphic = (Graphic)(object)component;
				_toggle.graphic = (Graphic)(object)component4;
				((UnityEvent<bool>)(object)_toggle.onValueChanged).AddListener((UnityAction<bool>)delegate(bool value)
				{
					Container currentContainer = __instance.m_currentContainer;
					ChestSelection.TrySet(__instance, currentContainer, value);
					Refresh(__instance);
				});
				val.SetActive(false);
			}
		}

		internal static void ContainerChanged(InventoryGui gui)
		{
			if (_container == gui.m_currentContainer)
			{
				if ((Object)(object)_toggle != (Object)null && ((Component)_toggle).gameObject.activeSelf && !((Selectable)_toggle).interactable)
				{
					Refresh(gui);
				}
			}
			else
			{
				_container = gui.m_currentContainer;
				ChestFinder.Invalidate();
				Refresh(gui);
			}
		}

		internal static void Refresh(InventoryGui gui)
		{
			if (!((Object)(object)_toggle == (Object)null) && !((Object)(object)gui == (Object)null))
			{
				Container currentContainer = gui.m_currentContainer;
				bool flag = Plugin.OnlyUseMarkedChests.Value && ChestSelection.IsSupported(currentContainer);
				((Component)_toggle).gameObject.SetActive(flag);
				if (flag)
				{
					_toggle.SetIsOnWithoutNotify(ChestSelection.IsMarked(currentContainer));
					((Selectable)_toggle).interactable = ChestSelection.CanEdit(gui, currentContainer);
				}
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateContainer")]
	internal static class ChestSelectionUI_Update_Patch
	{
		private static void Postfix(InventoryGui __instance)
		{
			ChestSelectionUI.ContainerChanged(__instance);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "Hide")]
	internal static class ChestSelectionUI_Hide_Patch
	{
		private static void Postfix(InventoryGui __instance)
		{
			ChestSelectionUI.ContainerChanged(__instance);
		}
	}
	internal static class Scope
	{
		internal static int Craft;

		internal static int Build;

		internal static int Consume;

		internal static int Station;

		internal static bool Counting
		{
			get
			{
				if ((Craft <= 0 || !Plugin.CraftFromChests.Value) && (Build <= 0 || !Plugin.BuildFromChests.Value))
				{
					return Station > 0;
				}
				return true;
			}
		}

		internal static bool Spending
		{
			get
			{
				if (Consume > 0)
				{
					if (Station <= 0 && !Plugin.CraftFromChests.Value)
					{
						return Plugin.BuildFromChests.Value;
					}
					return true;
				}
				return false;
			}
		}

		internal static bool IsLocalPlayerInventory(Inventory inv, out Player player)
		{
			player = Player.m_localPlayer;
			if ((Object)(object)player != (Object)null)
			{
				return inv == ((Humanoid)player).GetInventory();
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "HaveRequirementItems")]
	internal static class Player_HaveRequirementItems_Patch
	{
		private static void Prefix()
		{
			Scope.Craft++;
		}

		private static void Finalizer()
		{
			Scope.Craft--;
		}
	}
	[HarmonyPatch(typeof(Player), "GetFirstRequiredItem")]
	internal static class Player_GetFirstRequiredItem_Patch
	{
		private static void Prefix()
		{
			Scope.Craft++;
		}

		private static void Finalizer()
		{
			Scope.Craft--;
		}
	}
	[HarmonyPatch(typeof(Player), "HaveRequirements", new Type[]
	{
		typeof(Piece),
		typeof(RequirementMode)
	})]
	internal static class Player_HaveRequirementsPiece_Patch
	{
		private static void Prefix()
		{
			Scope.Build++;
		}

		private static void Finalizer()
		{
			Scope.Build--;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "SetupRequirement")]
	internal static class InventoryGui_SetupRequirement_Patch
	{
		private static void Prefix(bool craft)
		{
			if (craft)
			{
				Scope.Craft++;
			}
			else
			{
				Scope.Build++;
			}
		}

		private static void Postfix(Transform elementRoot, Requirement req, Player player, int quality, int craftMultiplier, bool __result)
		{
			if (__result && Plugin.ShowAvailableCounts.Value && !((Object)(object)req.m_resItem == (Object)null) && !((Object)(object)player == (Object)null))
			{
				int num = req.GetAmount(quality) * craftMultiplier;
				Transform obj = elementRoot.Find("res_amount");
				TMP_Text val = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null);
				if (num > 0 && !((Object)(object)val == (Object)null))
				{
					int n = ((Humanoid)player).GetInventory().CountItems(req.m_resItem.m_itemData.m_shared.m_name, -1, true);
					val.text = $"{Compact(n)}/{num}";
				}
			}
		}

		private static void Finalizer(bool craft)
		{
			if (craft)
			{
				Scope.Craft--;
			}
			else
			{
				Scope.Build--;
			}
		}

		private static string Compact(int n)
		{
			if (n < 1000)
			{
				return n.ToString();
			}
			if (n < 10000)
			{
				return ((float)n / 1000f).ToString("0.#", CultureInfo.InvariantCulture) + "k";
			}
			return n / 1000 + "k";
		}
	}
	[HarmonyPatch(typeof(Player), "ConsumeResources")]
	internal static class Player_ConsumeResources_Patch
	{
		private static void Prefix()
		{
			Scope.Consume++;
		}

		private static void Finalizer()
		{
			Scope.Consume--;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	internal static class InventoryGui_DoCrafting_Patch
	{
		private static void Prefix()
		{
			Scope.Consume++;
		}

		private static void Finalizer()
		{
			Scope.Consume--;
		}
	}
	[HarmonyPatch(typeof(Inventory), "CountItems")]
	internal static class Inventory_CountItems_Patch
	{
		private static void Postfix(Inventory __instance, string name, int quality, bool matchWorldLevel, ref int __result)
		{
			if (name != null && Scope.Counting && Scope.IsLocalPlayerInventory(__instance, out var player))
			{
				__result += ChestFinder.Count(player, name, quality, matchWorldLevel);
			}
		}
	}
	[HarmonyPatch(typeof(Inventory), "HaveItem", new Type[]
	{
		typeof(string),
		typeof(bool)
	})]
	internal static class Inventory_HaveItem_Patch
	{
		private static void Postfix(Inventory __instance, string name, bool matchWorldLevel, ref bool __result)
		{
			if (!__result && Scope.Counting && Scope.IsLocalPlayerInventory(__instance, out var player))
			{
				__result = ChestFinder.Have(player, name, matchWorldLevel);
			}
		}
	}
	[HarmonyPatch(typeof(Inventory), "GetItem", new Type[]
	{
		typeof(string),
		typeof(int),
		typeof(bool)
	})]
	internal static class Inventory_GetItem_Patch
	{
		private static void Postfix(Inventory __instance, string name, int quality, bool isPrefabName, ref ItemData __result)
		{
			if (!(__result != null || isPrefabName) && Scope.Counting && Scope.IsLocalPlayerInventory(__instance, out var player))
			{
				__result = ChestFinder.Find(player, name, quality);
			}
		}
	}
	[HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[]
	{
		typeof(string),
		typeof(int),
		typeof(int),
		typeof(bool)
	})]
	internal static class Inventory_RemoveItemByName_Patch
	{
		private static void Prefix(Inventory __instance, string name, int amount, int itemQuality, bool worldLevelBased)
		{
			if (!Scope.Spending || !Scope.IsLocalPlayerInventory(__instance, out var player))
			{
				return;
			}
			int num = 0;
			foreach (ItemData allItem in __instance.GetAllItems())
			{
				if (allItem.m_shared.m_name == name && (itemQuality < 0 || allItem.m_quality == itemQuality) && (!worldLevelBased || allItem.m_worldLevel >= Game.m_worldLevel))
				{
					num += allItem.m_stack;
				}
			}
			if (num < amount)
			{
				ChestFinder.Remove(player, name, amount - num, itemQuality, worldLevelBased);
			}
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class Container_Awake_Patch
	{
		private static void Postfix(Container __instance)
		{
			if (__instance.GetInventory() != null)
			{
				ChestFinder.Register(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Container), "OnContainerChanged")]
	internal static class Container_OnContainerChanged_Patch
	{
		private static void Postfix()
		{
			ChestFinder.InvalidateCounts();
		}
	}
	internal static class IgnoredSlots
	{
		private const string Key = "NearbyChests.IgnoredSlots";

		private static readonly HashSet<Vector2i> Slots = new HashSet<Vector2i>();

		private static Player _loadedFor;

		public static bool Contains(Player player, Vector2i pos)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			EnsureLoaded(player);
			return Slots.Contains(pos);
		}

		public static bool Toggle(Player player, Vector2i pos)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			EnsureLoaded(player);
			bool result;
			if (Slots.Remove(pos))
			{
				result = false;
			}
			else
			{
				Slots.Add(pos);
				result = true;
			}
			Save(player);
			return result;
		}

		private static void EnsureLoaded(Player player)
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)(object)_loadedFor)
			{
				return;
			}
			_loadedFor = player;
			Slots.Clear();
			if ((Object)(object)player == (Object)null || !player.m_customData.TryGetValue("NearbyChests.IgnoredSlots", out var value) || string.IsNullOrEmpty(value))
			{
				return;
			}
			string[] array = value.Split(';');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(',');
				if (array2.Length == 2 && int.TryParse(array2[0], out var result) && int.TryParse(array2[1], out var result2))
				{
					Slots.Add(new Vector2i(result, result2));
				}
			}
		}

		private static void Save(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			if (Slots.Count == 0)
			{
				player.m_customData.Remove("NearbyChests.IgnoredSlots");
				return;
			}
			player.m_customData["NearbyChests.IgnoredSlots"] = string.Join(";", Slots.Select((Vector2i s) => s.x + "," + s.y).ToArray());
		}
	}
	[HarmonyPatch(typeof(InventoryGrid), "UpdateGui")]
	internal static class InventoryGrid_UpdateGui_IgnoredSlots_Patch
	{
		private const string MarkerName = "NearbyChests_Ignored";

		private static void Postfix(InventoryGrid __instance, Player player)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.IgnoreSlots.Value && !((Object)(object)InventoryGui.instance == (Object)null) && !((Object)(object)__instance != (Object)(object)InventoryGui.instance.m_playerGrid))
			{
				int width = __instance.m_width;
				Vector2i pos = default(Vector2i);
				for (int i = 0; i < __instance.m_elements.Count; i++)
				{
					InventoryElement element = __instance.m_elements[i];
					Image obj = Marker(__instance, element);
					((Vector2i)(ref pos))..ctor(i % width, i / width);
					((Behaviour)obj).enabled = (Object)(object)player != (Object)null && IgnoredSlots.Contains(player, pos);
				}
			}
		}

		private static Image Marker(InventoryGrid grid, InventoryElement element)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: 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_009e: 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_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			Transform val = ((Component)element).transform.Find("NearbyChests_Ignored");
			if ((Object)(object)val != (Object)null)
			{
				return ((Component)val).GetComponent<Image>();
			}
			GameObject val2 = new GameObject("NearbyChests_Ignored", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			});
			RectTransform val3 = (RectTransform)val2.transform;
			((Transform)val3).SetParent(((Component)element).transform, false);
			Vector2 val4 = default(Vector2);
			((Vector2)(ref val4))..ctor(1f, 1f);
			val3.pivot = val4;
			Vector2 anchorMin = (val3.anchorMax = val4);
			val3.anchorMin = anchorMin;
			val3.anchoredPosition = new Vector2(-3f, -3f);
			val3.sizeDelta = new Vector2(18f, 18f);
			Image component = val2.GetComponent<Image>();
			component.sprite = PinIcon.Create();
			((Graphic)component).color = new Color(1f, 0.85f, 0.55f);
			component.preserveAspect = true;
			((Graphic)component).raycastTarget = false;
			((Behaviour)component).enabled = false;
			UIInputHandler componentInChildren = ((Component)element).GetComponentInChildren<UIInputHandler>();
			if ((Object)(object)componentInChildren != (Object)null)
			{
				componentInChildren.m_onMiddleClick = (Action<UIInputHandler>)Delegate.Combine(componentInChildren.m_onMiddleClick, (Action<UIInputHandler>)delegate
				{
					OnMiddleClick(grid, element);
				});
			}
			return component;
		}

		private static void OnMiddleClick(InventoryGrid grid, InventoryElement element)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null) && Plugin.IgnoreSlots.Value)
			{
				int num = grid.m_elements.IndexOf(element);
				if (num >= 0)
				{
					Vector2i val = default(Vector2i);
					((Vector2i)(ref val))..ctor(num % grid.m_width, num / grid.m_width);
					bool flag = IgnoredSlots.Toggle(localPlayer, val);
					ItemData itemAt = ((Humanoid)localPlayer).GetInventory().GetItemAt(val.x, val.y);
					string text = ((itemAt != null) ? Localization.instance.Localize(itemAt.m_shared.m_name) : "This slot");
					((Character)localPlayer).Message((MessageType)1, flag ? (text + ": Stack will leave it alone") : (text + ": Stack can move it again"), 0, (Sprite)null, false);
					grid.UpdateGui(localPlayer, InventoryGui.instance.m_dragItem);
				}
			}
		}
	}
	internal static class PinIcon
	{
		private static Sprite _sprite;

		public static Sprite Create()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: 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_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_sprite != (Object)null)
			{
				return _sprite;
			}
			Texture2D val = new Texture2D(32, 32, (TextureFormat)4, false)
			{
				filterMode = (FilterMode)1,
				wrapMode = (TextureWrapMode)1
			};
			Color[] array = (Color[])(object)new Color[1024];
			Color val2 = default(Color);
			((Color)(ref val2))..ctor(1f, 1f, 1f, 0f);
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = val2;
			}
			Vector2 val3 = default(Vector2);
			((Vector2)(ref val3))..ctor(20f, 20f);
			Vector2 val4 = default(Vector2);
			((Vector2)(ref val4))..ctor(4f, 4f);
			for (int j = 0; j < 32; j++)
			{
				for (int k = 0; k < 32; k++)
				{
					Vector2 val5 = new Vector2((float)k + 0.5f, (float)j + 0.5f);
					float num = Vector2.Distance(val5, val3) - 7.5f;
					Vector2 val6 = val3 - val4;
					float num2 = Mathf.Clamp01(Vector2.Dot(val5 - val4, val6) / ((Vector2)(ref val6)).sqrMagnitude);
					float num3 = Mathf.Lerp(0.5f, 2.5f, num2);
					float num4 = Vector2.Distance(val5, val4 + val6 * num2) - num3;
					float num5 = Mathf.Min(num, num4);
					float num6 = Mathf.Clamp01(0.5f - num5);
					if (num6 > 0f)
					{
						array[j * 32 + k] = new Color(1f, 1f, 1f, num6);
					}
				}
			}
			for (int l = 0; l < 32; l++)
			{
				for (int m = 0; m < 32; m++)
				{
					float num7 = Vector2.Distance(new Vector2((float)m + 0.5f, (float)l + 0.5f), val3 + new Vector2(1f, 1f));
					if (num7 < 2.5f)
					{
						array[l * 32 + m] = new Color(1f, 1f, 1f, Mathf.Clamp01(num7 - 1.5f));
					}
				}
			}
			val.SetPixels(array);
			val.Apply();
			_sprite = Sprite.Create(val, new Rect(0f, 0f, 32f, 32f), new Vector2(0.5f, 0.5f));
			return _sprite;
		}
	}
	internal static class ItemGroups
	{
		private const string DefaultFile = "# Nearby Chests - item groups\n#\n# When you stack, an item that no nearby chest holds yet goes to the chest holding the most\n# items from the same group. If no chest has anything from its group, it goes into an empty chest.\n#\n# Raw = meat, fish, eggs and other animal ingredients, plus uncooked oven dishes.\n# Plants = berries, mushrooms, vegetables, grain, flour, spices and other plant ingredients.\n# Anything listed here gets placed this way, whatever its item type. The exception is food, ammo and\n# equipment: while the ExcludeFood/ExcludeAmmo/ExcludeEquipment settings are on, those never leave\n# your inventory, so the Cooked group only affects how chests are sorted.\n#\n# One group per line:   GroupName = PrefabName, PrefabName, ...\n# - Names are item prefab names (the ones used by the 'spawn' console command). Not case sensitive.\n# - A trailing * matches any name starting with that text, e.g. Trophy*\n# - If an item is listed in more than one group, the first group wins, so order matters.\n# - Items not listed here are only placed if their type is in UnassignedItemTypes in the main\n#   config. They're grouped by item type (all trophies together, and so on), except materials,\n#   which all share one catch-all chest rather than taking a chest each.\n# - Delete this file to get the latest defaults back.\n# Save the file and the change applies the next time you stack; no restart needed.\n\nMetals = CopperOre, Copper, CopperScrap, TinOre, Tin, Bronze, BronzeScrap, BronzeNails, IronOre, IronScrap, Iron, IronNails, SilverOre, Silver, BlackMetalScrap, BlackMetal, FlametalOre, Flametal, FlametalOreNew, FlametalNew, GoldOre, Gold, Coal, MechanicalSpring, CharredCogwheel\nHides = LeatherScraps, DeerHide, TrollHide, WolfPelt, LoxPelt, ScaleHide, AskHide, MooseHide, BjornHide, SealHide, SerpentScale, BonemawSerpentScale, Leatherstraps, Feathers, BoneFragments\nWood = Wood, FineWood, RoundLog, ElderBark, YggdrasilWood, Blackwood, Frostwood\nStone = Stone, Flint, Obsidian, BlackMarble, Grausten, StoneRock\nSeeds = Acorn, BeechSeeds, BirchSeeds, FirCone, FirConeFrost, PineCone, CarrotSeeds, TurnipSeeds, OnionSeeds, KaleSeeds, OatSeeds, PoteitrSeeds\nFishing = FishingBait*, Hook\nRaw = RawMeat, DeerMeat, WolfMeat, LoxMeat, SerpentMeat, ChickenMeat, HareMeat, BugMeat, AsksvinMeat, VoltureMeat, BjornMeat, MooseMeat, BoneMawSerpentMeat, SealBlubber, NeckTail, Entrails, FishRaw, FishAnglerRaw, ChickenEgg, AsksvinEgg, VoltureEgg, Bloodbag, CuredSquirrelHamstring, Honey, RoyalJelly, FishAndBreadUncooked, HoneyGlazedChickenUncooked, MeatPlatterUncooked, MisthareSupremeUncooked, BakedPoteitrUncooked, KaleChipsUncooked, LoxPieUncooked, MagicallyStuffedShroomUncooked, OvenPancakeUncooked, PiquantPieUncooked, RoastedCrustPieUncooked, VikingCupcakeUncooked\nPlants = Raspberry, Blueberries, Cloudberry, Lingonberry, Vineberry, Pukeberries, Mushroom, MushroomYellow, MushroomBlue, MushroomJotunPuffs, MushroomMagecap, MushroomSmokePuff, Carrot, Turnip, Onion, Kale, Oat, Poteitr, Fiddleheadfern, Dandelion, Barley, BarleyFlour, OatFlour, BreadDough, Thistle, FreshSeaweed, Spice*\nCooked = Cooked*, BakedPoteitr, BlackSoup, BloodPudding, BoarJerky, Bread, CarrotSoup, DeerStew, Eyescream, FierySvinstew, FishAndBread, FishCooked, FishSoup, FishWraps, HoneyGlazedChicken, KaleChips, LoxPie, MagicallyStuffedShroom, MarinatedGreens, MashedMeat, MeatPlatter, MeatballsMashedPoteitr, MinceMeatSauce, MisthareSupreme, MooseKebab, MushroomOmelette, NeckTailGrilled, OatMilk, OatmealLingonberryJam, OnionSoup, OvenPancake, Pancakes, PiquantPie, PulledBear, QueensJam, RoastedCrustPie, Salad, Sausages, ScorchingMedley, SealSoup, SeekerAspic, SerpentMeatCooked, SerpentStew, ShocklateSmoothie, SizzlingBerryBroth, SmokedFish, SmokedMooseMeat, SparklingShroomshake, SpicyMarmalade, TurnipStew, VikingCupcake, WolfJerky, WolfMeatSkewer, YggdrasilPorridge\n# Coins are left out on purpose so you keep them for the trader.\nValuables = Amber, AmberPearl, Ruby, SilverNecklace, AncientCoin, AncientGemstone*, CrownJewel\nTrophies = Trophy*\n\nBlackForest = GreydwarfEye, SurtlingCore, AncientSeed, Resin, QueenBee, HardAntler, BjornPaw, Ectoplasm\nMountainsSwamp = Guck, Ooze, Root, WitheredBone, Crystal, FreezeGland, WolfFang, WolfClaw, DragonTear, PowderedDragonEgg, Chain\nPlainsOcean = Flax, LinenThread, Needle, Tar, Chitin\nMistlands = Sap, Softtissue, Carapace, Mandible, Eitr, Bilebag, GiantBloodSack, Wisp, DvergrNeedle, BlackCore\nAshlands = AskBladder, CharcoalResin, CharredBone, Charredskull, CelestialFeather, MoltenCore, MorgenHeart, MorgenSinew, ProustitePowder, SulfurStone, BonemawSerpentTooth, GemstoneRed, GemstoneGreen, GemstoneBlue, AsksvinCarrionNeck, AsksvinCarrionPelvic, AsksvinCarrionRibcage, AsksvinCarrionSkull\nDeepNorth = MooseSinew, Ice, FrozenFuel, OozeMork, ElakingHairBundle, UndeadBjornRibcage, BarkaBranch\n";

		private static readonly Dictionary<string, string> Exact = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private static readonly List<KeyValuePair<string, string>> Prefixes = new List<KeyValuePair<string, string>>();

		private static readonly Dictionary<string, string> Resolved = new Dictionary<string, string>();

		private static readonly List<string> Order = new List<string>();

		private static DateTime _loadedWriteTime;

		private const string TypePrefix = "type:";

		public static string FilePath => Path.Combine(Paths.ConfigPath, "NearbyChests.groups.txt");

		public static void Load()
		{
			try
			{
				if (!File.Exists(FilePath))
				{
					File.WriteAllText(FilePath, "# Nearby Chests - item groups\n#\n# When you stack, an item that no nearby chest holds yet goes to the chest holding the most\n# items from the same group. If no chest has anything from its group, it goes into an empty chest.\n#\n# Raw = meat, fish, eggs and other animal ingredients, plus uncooked oven dishes.\n# Plants = berries, mushrooms, vegetables, grain, flour, spices and other plant ingredients.\n# Anything listed here gets placed this way, whatever its item type. The exception is food, ammo and\n# equipment: while the ExcludeFood/ExcludeAmmo/ExcludeEquipment settings are on, those never leave\n# your inventory, so the Cooked group only affects how chests are sorted.\n#\n# One group per line:   GroupName = PrefabName, PrefabName, ...\n# - Names are item prefab names (the ones used by the 'spawn' console command). Not case sensitive.\n# - A trailing * matches any name starting with that text, e.g. Trophy*\n# - If an item is listed in more than one group, the first group wins, so order matters.\n# - Items not listed here are only placed if their type is in UnassignedItemTypes in the main\n#   config. They're grouped by item type (all trophies together, and so on), except materials,\n#   which all share one catch-all chest rather than taking a chest each.\n# - Delete this file to get the latest defaults back.\n# Save the file and the change applies the next time you stack; no restart needed.\n\nMetals = CopperOre, Copper, CopperScrap, TinOre, Tin, Bronze, BronzeScrap, BronzeNails, IronOre, IronScrap, Iron, IronNails, SilverOre, Silver, BlackMetalScrap, BlackMetal, FlametalOre, Flametal, FlametalOreNew, FlametalNew, GoldOre, Gold, Coal, MechanicalSpring, CharredCogwheel\nHides = LeatherScraps, DeerHide, TrollHide, WolfPelt, LoxPelt, ScaleHide, AskHide, MooseHide, BjornHide, SealHide, SerpentScale, BonemawSerpentScale, Leatherstraps, Feathers, BoneFragments\nWood = Wood, FineWood, RoundLog, ElderBark, YggdrasilWood, Blackwood, Frostwood\nStone = Stone, Flint, Obsidian, BlackMarble, Grausten, StoneRock\nSeeds = Acorn, BeechSeeds, BirchSeeds, FirCone, FirConeFrost, PineCone, CarrotSeeds, TurnipSeeds, OnionSeeds, KaleSeeds, OatSeeds, PoteitrSeeds\nFishing = FishingBait*, Hook\nRaw = RawMeat, DeerMeat, WolfMeat, LoxMeat, SerpentMeat, ChickenMeat, HareMeat, BugMeat, AsksvinMeat, VoltureMeat, BjornMeat, MooseMeat, BoneMawSerpentMeat, SealBlubber, NeckTail, Entrails, FishRaw, FishAnglerRaw, ChickenEgg, AsksvinEgg, VoltureEgg, Bloodbag, CuredSquirrelHamstring, Honey, RoyalJelly, FishAndBreadUncooked, HoneyGlazedChickenUncooked, MeatPlatterUncooked, MisthareSupremeUncooked, BakedPoteitrUncooked, KaleChipsUncooked, LoxPieUncooked, MagicallyStuffedShroomUncooked, OvenPancakeUncooked, PiquantPieUncooked, RoastedCrustPieUncooked, VikingCupcakeUncooked\nPlants = Raspberry, Blueberries, Cloudberry, Lingonberry, Vineberry, Pukeberries, Mushroom, MushroomYellow, MushroomBlue, MushroomJotunPuffs, MushroomMagecap, MushroomSmokePuff, Carrot, Turnip, Onion, Kale, Oat, Poteitr, Fiddleheadfern, Dandelion, Barley, BarleyFlour, OatFlour, BreadDough, Thistle, FreshSeaweed, Spice*\nCooked = Cooked*, BakedPoteitr, BlackSoup, BloodPudding, BoarJerky, Bread, CarrotSoup, DeerStew, Eyescream, FierySvinstew, FishAndBread, FishCooked, FishSoup, FishWraps, HoneyGlazedChicken, KaleChips, LoxPie, MagicallyStuffedShroom, MarinatedGreens, MashedMeat, MeatPlatter, MeatballsMashedPoteitr, MinceMeatSauce, MisthareSupreme, MooseKebab, MushroomOmelette, NeckTailGrilled, OatMilk, OatmealLingonberryJam, OnionSoup, OvenPancake, Pancakes, PiquantPie, PulledBear, QueensJam, RoastedCrustPie, Salad, Sausages, ScorchingMedley, SealSoup, SeekerAspic, SerpentMeatCooked, SerpentStew, ShocklateSmoothie, SizzlingBerryBroth, SmokedFish, SmokedMooseMeat, SparklingShroomshake, SpicyMarmalade, TurnipStew, VikingCupcake, WolfJerky, WolfMeatSkewer, YggdrasilPorridge\n# Coins are left out on purpose so you keep them for the trader.\nValuables = Amber, AmberPearl, Ruby, SilverNecklace, AncientCoin, AncientGemstone*, CrownJewel\nTrophies = Trophy*\n\nBlackForest = GreydwarfEye, SurtlingCore, AncientSeed, Resin, QueenBee, HardAntler, BjornPaw, Ectoplasm\nMountainsSwamp = Guck, Ooze, Root, WitheredBone, Crystal, FreezeGland, WolfFang, WolfClaw, DragonTear, PowderedDragonEgg, Chain\nPlainsOcean = Flax, LinenThread, Needle, Tar, Chitin\nMistlands = Sap, Softtissue, Carapace, Mandible, Eitr, Bilebag, GiantBloodSack, Wisp, DvergrNeedle, BlackCore\nAshlands = AskBladder, CharcoalResin, CharredBone, Charredskull, CelestialFeather, MoltenCore, MorgenHeart, MorgenSinew, ProustitePowder, SulfurStone, BonemawSerpentTooth, GemstoneRed, GemstoneGreen, GemstoneBlue, AsksvinCarrionNeck, AsksvinCarrionPelvic, AsksvinCarrionRibcage, AsksvinCarrionSkull\nDeepNorth = MooseSinew, Ice, FrozenFuel, OozeMork, ElakingHairBundle, UndeadBjornRibcage, BarkaBranch\n");
				}
				_loadedWriteTime = File.GetLastWriteTimeUtc(FilePath);
				Parse(File.ReadAllLines(FilePath));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Couldn't read " + FilePath + ", using built-in groups: " + ex.Message));
				Parse("# Nearby Chests - item groups\n#\n# When you stack, an item that no nearby chest holds yet goes to the chest holding the most\n# items from the same group. If no chest has anything from its group, it goes into an empty chest.\n#\n# Raw = meat, fish, eggs and other animal ingredients, plus uncooked oven dishes.\n# Plants = berries, mushrooms, vegetables, grain, flour, spices and other plant ingredients.\n# Anything listed here gets placed this way, whatever its item type. The exception is food, ammo and\n# equipment: while the ExcludeFood/ExcludeAmmo/ExcludeEquipment settings are on, those never leave\n# your inventory, so the Cooked group only affects how chests are sorted.\n#\n# One group per line:   GroupName = PrefabName, PrefabName, ...\n# - Names are item prefab names (the ones used by the 'spawn' console command). Not case sensitive.\n# - A trailing * matches any name starting with that text, e.g. Trophy*\n# - If an item is listed in more than one group, the first group wins, so order matters.\n# - Items not listed here are only placed if their type is in UnassignedItemTypes in the main\n#   config. They're grouped by item type (all trophies together, and so on), except materials,\n#   which all share one catch-all chest rather than taking a chest each.\n# - Delete this file to get the latest defaults back.\n# Save the file and the change applies the next time you stack; no restart needed.\n\nMetals = CopperOre, Copper, CopperScrap, TinOre, Tin, Bronze, BronzeScrap, BronzeNails, IronOre, IronScrap, Iron, IronNails, SilverOre, Silver, BlackMetalScrap, BlackMetal, FlametalOre, Flametal, FlametalOreNew, FlametalNew, GoldOre, Gold, Coal, MechanicalSpring, CharredCogwheel\nHides = LeatherScraps, DeerHide, TrollHide, WolfPelt, LoxPelt, ScaleHide, AskHide, MooseHide, BjornHide, SealHide, SerpentScale, BonemawSerpentScale, Leatherstraps, Feathers, BoneFragments\nWood = Wood, FineWood, RoundLog, ElderBark, YggdrasilWood, Blackwood, Frostwood\nStone = Stone, Flint, Obsidian, BlackMarble, Grausten, StoneRock\nSeeds = Acorn, BeechSeeds, BirchSeeds, FirCone, FirConeFrost, PineCone, CarrotSeeds, TurnipSeeds, OnionSeeds, KaleSeeds, OatSeeds, PoteitrSeeds\nFishing = FishingBait*, Hook\nRaw = RawMeat, DeerMeat, WolfMeat, LoxMeat, SerpentMeat, ChickenMeat, HareMeat, BugMeat, AsksvinMeat, VoltureMeat, BjornMeat, MooseMeat, BoneMawSerpentMeat, SealBlubber, NeckTail, Entrails, FishRaw, FishAnglerRaw, ChickenEgg, AsksvinEgg, VoltureEgg, Bloodbag, CuredSquirrelHamstring, Honey, RoyalJelly, FishAndBreadUncooked, HoneyGlazedChickenUncooked, MeatPlatterUncooked, MisthareSupremeUncooked, BakedPoteitrUncooked, KaleChipsUncooked, LoxPieUncooked, MagicallyStuffedShroomUncooked, OvenPancakeUncooked, PiquantPieUncooked, RoastedCrustPieUncooked, VikingCupcakeUncooked\nPlants = Raspberry, Blueberries, Cloudberry, Lingonberry, Vineberry, Pukeberries, Mushroom, MushroomYellow, MushroomBlue, MushroomJotunPuffs, MushroomMagecap, MushroomSmokePuff, Carrot, Turnip, Onion, Kale, Oat, Poteitr, Fiddleheadfern, Dandelion, Barley, BarleyFlour, OatFlour, BreadDough, Thistle, FreshSeaweed, Spice*\nCooked = Cooked*, BakedPoteitr, BlackSoup, BloodPudding, BoarJerky, Bread, CarrotSoup, DeerStew, Eyescream, FierySvinstew, FishAndBread, FishCooked, FishSoup, FishWraps, HoneyGlazedChicken, KaleChips, LoxPie, MagicallyStuffedShroom, MarinatedGreens, MashedMeat, MeatPlatter, MeatballsMashedPoteitr, MinceMeatSauce, MisthareSupreme, MooseKebab, MushroomOmelette, NeckTailGrilled, OatMilk, OatmealLingonberryJam, OnionSoup, OvenPancake, Pancakes, PiquantPie, PulledBear, QueensJam, RoastedCrustPie, Salad, Sausages, ScorchingMedley, SealSoup, SeekerAspic, SerpentMeatCooked, SerpentStew, ShocklateSmoothie, SizzlingBerryBroth, SmokedFish, SmokedMooseMeat, SparklingShroomshake, SpicyMarmalade, TurnipStew, VikingCupcake, WolfJerky, WolfMeatSkewer, YggdrasilPorridge\n# Coins are left out on purpose so you keep them for the trader.\nValuables = Amber, AmberPearl, Ruby, SilverNecklace, AncientCoin, AncientGemstone*, CrownJewel\nTrophies = Trophy*\n\nBlackForest = GreydwarfEye, SurtlingCore, AncientSeed, Resin, QueenBee, HardAntler, BjornPaw, Ectoplasm\nMountainsSwamp = Guck, Ooze, Root, WitheredBone, Crystal, FreezeGland, WolfFang, WolfClaw, DragonTear, PowderedDragonEgg, Chain\nPlainsOcean = Flax, LinenThread, Needle, Tar, Chitin\nMistlands = Sap, Softtissue, Carapace, Mandible, Eitr, Bilebag, GiantBloodSack, Wisp, DvergrNeedle, BlackCore\nAshlands = AskBladder, CharcoalResin, CharredBone, Charredskull, CelestialFeather, MoltenCore, MorgenHeart, MorgenSinew, ProustitePowder, SulfurStone, BonemawSerpentTooth, GemstoneRed, GemstoneGreen, GemstoneBlue, AsksvinCarrionNeck, AsksvinCarrionPelvic, AsksvinCarrionRibcage, AsksvinCarrionSkull\nDeepNorth = MooseSinew, Ice, FrozenFuel, OozeMork, ElakingHairBundle, UndeadBjornRibcage, BarkaBranch\n".Split('\n'));
			}
		}

		public static void ReloadIfChanged()
		{
			try
			{
				if (File.Exists(FilePath) && File.GetLastWriteTimeUtc(FilePath) != _loadedWriteTime)
				{
					Load();
				}
			}
			catch (IOException)
			{
			}
		}

		private static void Parse(IEnumerable<string> lines)
		{
			Exact.Clear();
			Prefixes.Clear();
			Resolved.Clear();
			Order.Clear();
			foreach (string line in lines)
			{
				string text = line.Trim();
				if (text.Length == 0 || text.StartsWith("#"))
				{
					continue;
				}
				int num = text.IndexOf('=');
				if (num <= 0)
				{
					continue;
				}
				string text2 = text.Substring(0, num).Trim();
				if (!Order.Contains(text2))
				{
					Order.Add(text2);
				}
				string[] array = text.Substring(num + 1).Split(',');
				for (int i = 0; i < array.Length; i++)
				{
					string text3 = array[i].Trim();
					if (text3.Length != 0)
					{
						if (text3.EndsWith("*"))
						{
							Prefixes.Add(new KeyValuePair<string, string>(text3.TrimEnd('*'), text2));
						}
						else if (!Exact.ContainsKey(text3))
						{
							Exact[text3] = text2;
						}
					}
				}
			}
		}

		public static int Rank(ItemData item)
		{
			return RankOf(Of(item));
		}

		public static int RankOf(string group)
		{
			int num = Order.IndexOf(group ?? "");
			if (num >= 0)
			{
				return num;
			}
			return int.MaxValue;
		}

		public static bool IsListed(ItemData item)
		{
			string text = Of(item);
			if (text != null)
			{
				return !text.StartsWith("type:");
			}
			return false;
		}

		public unsafe static string Of(ItemData item)
		{
			//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_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Invalid comparison between Unknown and I4
			string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name);
			if (Resolved.TryGetValue(text, out var value))
			{
				return value;
			}
			string value2 = null;
			if (!Exact.TryGetValue(text, out value2))
			{
				foreach (KeyValuePair<string, string> prefix in Prefixes)
				{
					if (text.StartsWith(prefix.Key, StringComparison.OrdinalIgnoreCase))
					{
						value2 = prefix.Value;
						break;
					}
				}
			}
			ItemType itemType = item.m_shared.m_itemType;
			if (value2 == null && IsEquipment(itemType))
			{
				value2 = "type:Equipment";
			}
			else if (value2 == null && IsAmmo(itemType))
			{
				value2 = "type:Ammo";
			}
			else if (value2 == null && (int)itemType != 1)
			{
				value2 = "type:" + ((object)(*(ItemType*)(&itemType))/*cast due to .constrained prefix*/).ToString();
			}
			Resolved[text] = value2;
			return value2;
		}

		public static bool IsAmmo(ItemType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			if ((int)type != 9)
			{
				return (int)type == 23;
			}
			return true;
		}

		public static bool IsEquipment(ItemType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected I4, but got Unknown
			switch (type - 3)
			{
			case 0:
			case 1:
			case 2:
			case 3:
			case 4:
			case 7:
			case 8:
			case 9:
			case 11:
			case 12:
			case 14:
			case 15:
			case 16:
			case 17:
			case 19:
			case 21:
				return true;
			default:
				return false;
			}
		}
	}
	[BepInPlugin("NearbyChests", "Nearby Chests", "1.2.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "NearbyChests";

		public const string ModName = "Nearby Chests";

		public const string Version = "1.2.1";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> OnlyUseMarkedChests;

		internal static ConfigEntry<bool> IncludeCartsAndShips;

		internal static ConfigEntry<float> CraftingRange;

		internal static ConfigEntry<float> StackingRange;

		internal static ConfigEntry<bool> CraftFromChests;

		internal static ConfigEntry<bool> BuildFromChests;

		internal static ConfigEntry<bool> StationsFromChests;

		internal static ConfigEntry<bool> ShowAvailableCounts;

		internal static ConfigEntry<KeyboardShortcut> StackAndTidyKey;

		internal static ConfigEntry<bool> StackToNearby;

		internal static ConfigEntry<bool> KeepHotbar;

		internal static ConfigEntry<bool> ExcludeFood;

		internal static ConfigEntry<bool> ExcludeAmmo;

		internal static ConfigEntry<bool> ExcludeEquipment;

		internal static ConfigEntry<bool> PlaceUnassignedItems;

		internal static ConfigEntry<bool> DontFillEmptyChests;

		internal static ConfigEntry<string> UnassignedItemTypes;

		internal static ConfigEntry<bool> FallbackToOpenChest;

		internal static ConfigEntry<bool> FallbackToAnyChest;

		internal static ConfigEntry<bool> SortAfterStack;

		internal static ConfigEntry<bool> TidyButton;

		internal static ConfigEntry<bool> TidyGathers;

		internal static ConfigEntry<bool> ShareChests;

		internal static ConfigEntry<bool> IgnoreSlots;

		internal static ConfigEntry<bool> PullBuildMaterials;

		internal static ConfigEntry<KeyboardShortcut> PullBuildMaterialsKey;

		internal static ConfigEntry<int> PullBuildSets;

		internal static readonly HashSet<ItemType> UnassignedTypes = new HashSet<ItemType>();

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Expected O, but got Unknown
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Expected O, but got Unknown
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0396: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d7: Expected O, but got Unknown
			//IL_04b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c3: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			OnlyUseMarkedChests = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "OnlyUseMarkedChests", false, "Only use chests you have marked: tick Include in NearbyChests in a chest's window. Applies to everything the mod does - crafting, upgrading and building counts, station feeding, build-material pulls, Stack and Tidy. Stack and Tidy show a message if the open chest isn't marked. Turn this off to go back to using every chest in range.");
			IncludeCartsAndShips = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "IncludeCartsAndShips", false, "Also use the storage in nearby carts and ships.");
			CraftingRange = ((BaseUnityPlugin)this).Config.Bind<float>("Crafting", "CraftingRange", 30f, new ConfigDescription("How far (in meters) a chest can be and still be used for crafting, upgrading and building.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 60f), Array.Empty<object>()));
			CraftFromChests = ((BaseUnityPlugin)this).Config.Bind<bool>("Crafting", "CraftFromChests", true, "Use materials from nearby chests when crafting and upgrading at a station.");
			BuildFromChests = ((BaseUnityPlugin)this).Config.Bind<bool>("Crafting", "BuildFromChests", true, "Use materials from nearby chests when building with the hammer/hoe/cultivator.");
			StationsFromChests = ((BaseUnityPlugin)this).Config.Bind<bool>("Crafting", "StationsFromChests", true, "Take fuel and ore from nearby chests when feeding a station you aren't carrying them for: coal and ore into smelters and kilns, wood into fires, fuel into cooking stations. One press still adds one item, as in vanilla. Food and mead stay manual, because a cooking station or fermenter takes several different items and the mod would pick for you.");
			ShowAvailableCounts = ((BaseUnityPlugin)this).Config.Bind<bool>("Crafting", "ShowAvailableCounts", true, "Show how many of each material you have next to how many are needed, as have/needed, in the crafting panel and build menu. The count includes nearby chests.");
			StackingRange = ((BaseUnityPlugin)this).Config.Bind<float>("Stacking", "StackingRange", 15f, new ConfigDescription("How far (in meters) a chest can be and still be used by Stack, Tidy and sorting.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 60f), Array.Empty<object>()));
			StackAndTidyKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Stacking", "StackAndTidyKey", new KeyboardShortcut((KeyCode)0, Array.Empty<KeyCode>()), "Stack your inventory, then tidy each eligible chest within StackingRange once. Re-sorts every eligible chest in range, even if no items move. Works without opening a chest. None disables the shortcut. Independent of StackToNearby and TidyButton; respects chest and item restrictions.");
			StackToNearby = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "StackToNearby", true, "When you press the Stack button on an open chest, send your items to every nearby chest that already holds that item.");
			KeepHotbar = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "KeepHotbar", true, "Never stack items from your hotbar (the top row of your inventory).");
			ExcludeFood = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "ExcludeFood", true, "Never stack food, meads or potions you're carrying - anything cooked, baked, crafted or brewed. Edible things you pick or harvest (berries, mushrooms, honey...) count as ingredients and still get stacked.");
			ExcludeAmmo = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "ExcludeAmmo", true, "Never stack arrows, bolts, bait or other ammo you're carrying.");
			ExcludeEquipment = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "ExcludeEquipment", true, "Never stack weapons, armor, shields, tools, torches, utility items or trinkets.");
			DontFillEmptyChests = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "DontFillEmptyChests", false, "Don't automatically put items into completely empty chests. Manual deposits are still allowed. Takes effect immediately.");
			PlaceUnassignedItems = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "PlaceUnassignedItems", true, "Items that no nearby chest holds yet go to the chest with the most similar items (metals with metals, hides with hides, same biome...), or into an empty chest if none match. Groups are defined in NearbyChests.groups.txt. Items listed there are always placed; other items only if their type is in UnassignedItemTypes.");
			UnassignedItemTypes = ((BaseUnityPlugin)this).Config.Bind<string>("Stacking", "UnassignedItemTypes", "Material,Trophy", "Comma-separated item types that PlaceUnassignedItems may move when the item isn't listed in the groups file. Options: Material, Trophy, Consumable, Ammo, AmmoNonEquipable, Fish, Misc.");
			FallbackToOpenChest = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "FallbackToOpenChest", false, "If an unassigned item has no similar chest and there is no empty chest, put it in the chest you have open. When off, it stays in your inventory.");
			FallbackToAnyChest = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "FallbackToAnyChest", false, "If an item still has nowhere to go after every other rule, put it in any nearby chest with room (the open chest first, then the nearest) instead of leaving it in your inventory. Gets everything put away, at the cost of mixing items into chests of other categories; Tidy sorts them out later.");
			SortAfterStack = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "SortAfterStack", true, "Sort and merge the contents of every chest that received items.");
			ShareChests = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "ShareChests", true, "When a group has no chest of its own and there's no empty chest, let a chest that holds only one group take a second one, preferring related groups (neighbours in the groups file).");
			TidyGathers = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "TidyGathers", true, "Tidy also pulls items of the open chest's category out of chests where they don't belong (a shared chest's second group, strays in another chest, the junk chest). Chests of the same category are left alone.");
			TidyButton = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "TidyButton", true, "Show a Tidy button in the chest window. It moves items that don't match the open chest's category to a nearby chest of their own category, an empty chest (which becomes that category's chest), or a junk chest (one that's mostly uncategorized items), then sorts the chest. Takes effect after a restart.");
			IgnoreSlots = ((BaseUnityPlugin)this).Config.Bind<bool>("Stacking", "IgnoreSlots", true, "Middle-click a slot in your inventory to mark it ignored (a pin appears in its corner). Stack never moves whatever is in an ignored slot. Middle-click again to clear it. Marks are saved with your character.");
			PullBuildMaterials = ((BaseUnityPlugin)this).Config.Bind<bool>("Building", "PullBuildMaterials", true, "With a piece selected on your hammer, press PullBuildMaterialsKey to pull that piece's materials from nearby chests (within CraftingRange) into your inventory, for building somewhere out of range. Each press adds enough for one more piece on top of what you carry.");
			PullBuildMaterialsKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Building", "PullBuildMaterialsKey", new KeyboardShortcut((KeyCode)323, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "The key (with modifiers) that pulls the selected piece's materials. Default is Ctrl + left click.");
			PullBuildSets = ((BaseUnityPlugin)this).Config.Bind<int>("Building", "PullBuildSets", 1, new ConfigDescription("How many pieces' worth of materials each press adds.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50), Array.Empty<object>()));
			ParseUnassignedTypes();
			ItemGroups.Load();
			UnassignedItemTypes.SettingChanged += delegate
			{
				ParseUnassignedTypes();
			};
			OnlyUseMarkedChests.SettingChanged += delegate
			{
				ChestFinder.Invalidate();
				ChestSelectionUI.Refresh(InventoryGui.instance);
			};
			CraftingRange.SettingChanged += delegate
			{
				ChestFinder.Invalidate();
			};
			StackingRange.SettingChanged += delegate
			{
				ChestFinder.Invalidate();
			};
			IncludeCartsAndShips.SettingChanged += delegate
			{
				ChestFinder.Invalidate();
				ChestSelectionUI.Refresh(InventoryGui.instance);
			};
			_harmony = new Harmony("NearbyChests");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Nearby Chests 1.2.1 loaded");
		}

		private void Update()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			KeyboardShortcut value = StackAndTidyKey.Value;
			if ((Object)(object)localPlayer == (Object)null || (int)((KeyboardShortcut)(ref value)).MainKey == 0 || !((Character)localPlayer).TakeInput() || !ShortcutKeyPressed(((KeyboardShortcut)(ref value)).MainKey, down: true))
			{
				return;
			}
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref value)).Modifiers)
			{
				if (!ShortcutKeyPressed(modifier, down: false))
				{
					return;
				}
			}
			try
			{
				int num = Stacker.StackFromPlayer(null, showMessage: false);
				ChestFinder.Invalidate();
				List<Container> list = new List<Container>(ChestFinder.GetNearby(localPlayer, StackingRange.Value));
				int num2 = 0;
				foreach (Container item in list)
				{
					if (ChestFinder.IsUsable(item, localPlayer, StackingRange.Value) && Tidier.TidyChest(item, showMessage: false, list))
					{
						num2++;
					}
				}
				((Character)localPlayer).Message((MessageType)2, (list.Count == 0) ? "No eligible nearby chests" : string.Format("Stacked {0} items; tidied {1} {2}", num, num2, (num2 == 1) ? "chest" : "chests"), 0, (Sprite)null, false);
			}
			finally
			{
				ChestFinder.Invalidate();
			}
		}

		internal static bool ShortcutKeyPressed(KeyCode key, bool down)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if ((int)key >= 670 && (int)key <= 678)
			{
				Keyboard current = Keyboard.current;
				if (current == null)
				{
					return false;
				}
				Key val = (Key)(115 + key - 670);
				KeyControl val2 = current[val];
				if (!down)
				{
					return ((ButtonControl)val2).isPressed;
				}
				return ((ButtonControl)val2).wasPressedThisFrame;
			}
			if (!down)
			{
				return ZInput.GetKey(key, true);
			}
			return ZInput.GetKeyDown(key, true);
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private static void ParseUnassignedTypes()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			UnassignedTypes.Clear();
			string[] array = UnassignedItemTypes.Value.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text in array)
			{
				try
				{
					UnassignedTypes.Add((ItemType)Enum.Parse(typeof(ItemType), text.Trim(), ignoreCase: true));
				}
				catch (ArgumentException)
				{
				}
			}
		}
	}
	internal static class Stacker
	{
		private static HashSet<string> _madeItems;

		private static int _madeFromRecipes = -1;

		public static void StackFromPlayer(Container opened)
		{
			StackFromPlayer(opened, showMessage: true);
		}

		internal static int StackFromPlayer(Container opened, bool showMessage)
		{
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0232: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return 0;
			}
			if ((Object)(object)opened != (Object)null && !ChestFinder.CanUseOpened(opened, localPlayer))
			{
				return 0;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			ChestFinder.Invalidate();
			List<Container> list = new List<Container>();
			if ((Object)(object)opened != (Object)null)
			{
				list.Add(opened);
			}
			foreach (Container item in ChestFinder.GetNearby(localPlayer, Plugin.StackingRange.Value))
			{
				if ((Object)(object)item != (Object)(object)opened)
				{
					list.Add(item);
				}
			}
			ItemGroups.ReloadIfChanged();
			HashSet<Container> hashSet = new HashSet<Container>();
			int num = 0;
			int num2 = 0;
			foreach (ItemData item2 in inventory.GetAllItems().ToList())
			{
				if (!CanStack(localPlayer, item2))
				{
					continue;
				}
				foreach (Container item3 in list)
				{
					if (item3.GetInventory().ContainsItemByName(item2.m_shared.m_name))
					{
						num += MoveInto(item3, item2, inventory, hashSet);
						if (!inventory.ContainsItem(item2))
						{
							break;
						}
					}
				}
				if (inventory.ContainsItem(item2) && Plugin.PlaceUnassignedItems.Value && (ItemGroups.IsListed(item2) || Plugin.UnassignedTypes.Contains(item2.m_shared.m_itemType)))
				{
					num += PlaceUnassigned(item2, inventory, list, opened, hashSet);
					if (inventory.ContainsItem(item2))
					{
						num2 += item2.m_stack;
					}
				}
			}
			inventory.Changed(false, false);
			if (Plugin.SortAfterStack.Value)
			{
				foreach (Container item4 in hashSet)
				{
					Sort(item4.GetInventory());
				}
			}
			foreach (Container item5 in hashSet)
			{
				InventoryGui.instance.m_moveItemEffects.Create(((Component)item5).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
			}
			if (num > 0)
			{
				Game.instance.IncrementPlayerStat((PlayerStatType)82, 1f, false);
			}
			if (!showMessage)
			{
				return num;
			}
			string text = ((num2 == 0) ? "" : (Plugin.FallbackToAnyChest.Value ? $"\n{num2} items didn't fit in any nearby chest" : $"\n{num2} items had no similar or empty chest"));
			if (num > 0)
			{
				string arg = ((hashSet.Count == 1) ? "1 chest" : (hashSet.Count + " chests"));
				((Character)localPlayer).Message((MessageType)2, $"Stacked {num} items into {arg}{text}", 0, (Sprite)null, false);
			}
			else
			{
				((Character)localPlayer).Message((MessageType)2, (text.Length > 0) ? text.TrimStart('\n') : "$msg_stackall_none", 0, (Sprite)null, false);
			}
			return num;
		}

		private static int PlaceUnassigned(ItemData item, Inventory from, List<Container> chests, Container opened, HashSet<Container> touched)
		{
			int num = 0;
			string group = Tidier.GroupOf(item);
			object source;
			if (!(group == "\u0001junk"))
			{
				source = chests;
			}
			else
			{
				source = chests.Where((Container c) => Tidier.Category(c.GetInventory()) == "\u0001junk");
			}
			foreach (Container item2 in (from c in (IEnumerable<Container>)source
				select new
				{
					Chest = c,
					Score = GroupScore(c.GetInventory(), @group)
				} into x
				where x.Score > 0
				orderby x.Score descending
				select x.Chest).ToList())
			{
				num += MoveInto(item2, item, from, touched);
				if (!from.ContainsItem(item))
				{
					return num;
				}
			}
			foreach (Container chest in chests)
			{
				if (chest.GetInventory().NrOfItems() == 0)
				{
					num += MoveInto(chest, item, from, touched);
					if (!from.ContainsItem(item))
					{
						return num;
					}
				}
			}
			if (Plugin.ShareChests.Value)
			{
				num += Tidier.MoveToSharedChest(item, Tidier.GroupOf(item), from, chests, touched);
				if (!from.ContainsItem(item))
				{
					return num;
				}
			}
			if ((Object)(object)opened != (Object)null && Plugin.FallbackToOpenChest.Value && ChestSelection.Allows(opened))
			{
				num += MoveInto(opened, item, from, touched);
				if (!from.ContainsItem(item))
				{
					return num;
				}
			}
			if (Plugin.FallbackToAnyChest.Value)
			{
				foreach (Container chest2 in chests)
				{
					num += MoveInto(chest2, item, from, touched);
					if (!from.ContainsItem(item))
					{
						return num;
					}
				}
			}
			return num;
		}

		internal static int GroupScore(Inventory inv, string group)
		{
			int num = 0;
			foreach (ItemData allItem in inv.GetAllItems())
			{
				if (Tidier.GroupOf(allItem) == group)
				{
					num++;
				}
			}
			return num;
		}

		private static bool CanStack(Player player, ItemData item)
		{
			//IL_0059: 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_00af: Unknown result type (might be due to invalid IL or missing references)
			if (item.m_shared.m_maxStackSize <= 1 || item.m_shared.m_questItem)
			{
				return false;
			}
			if (item.m_equipped || ((Humanoid)player).IsItemEquiped(item))
			{
				return false;
			}
			if (Plugin.KeepHotbar.Value && item.m_gridPos.y == 0)
			{
				return false;
			}
			if (Plugin.IgnoreSlots.Value && IgnoredSlots.Contains(player, item.m_gridPos))
			{
				return false;
			}
			if (Plugin.ExcludeFood.Value && IsFood(item))
			{
				return false;
			}
			if (Plugin.ExcludeAmmo.Value && ItemGroups.IsAmmo(item.m_shared.m_itemType))
			{
				return false;
			}
			if (Plugin.ExcludeEquipment.Value && ItemGroups.IsEquipment(item.m_shared.m_itemType))
			{
				return false;
			}
			return true;
		}

		private static bool IsFood(ItemData item)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			if ((int)item.m_shared.m_itemType != 2)
			{
				return false;
			}
			return MadeItems()?.Contains(item.m_shared.m_name) ?? true;
		}

		private static HashSet<string> MadeItems()
		{
			ObjectDB instance = ObjectDB.instance;
			ZNetScene instance2 = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null)
			{
				return null;
			}
			if (_madeItems != null && _madeFromRecipes == instance.m_recipes.Count)
			{
				return _madeItems;
			}
			HashSet<string> hashSet = new HashSet<string>();
			foreach (Recipe recipe in instance.m_recipes)
			{
				AddMade(hashSet, ((Object)(object)recipe != (Object)null) ? recipe.m_item : null);
			}
			foreach (GameObject prefab in instance2.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				CookingStation component = prefab.GetComponent<CookingStation>();
				if ((Object)(object)component != (Object)null)
				{
					foreach (ItemConversion item in component.m_conversion)
					{
						AddMade(hashSet, item.m_to);
					}
				}
				Fermenter component2 = prefab.GetComponent<Fermenter>();
				if ((Object)(object)component2 != (Object)null)
				{
					foreach (ItemConversion item2 in component2.m_conversion)
					{
						AddMade(hashSet, item2.m_to);
					}
				}
				Smelter component3 = prefab.GetComponent<Smelter>();
				if (!((Object)(object)component3 != (Object)null))
				{
					continue;
				}
				foreach (ItemConversion item3 in component3.m_conversion)
				{
					AddMade(hashSet, item3.m_to);
				}
			}
			_madeItems = hashSet;
			_madeFromRecipes = instance.m_recipes.Count;
			return hashSet;
		}

		private static void AddMade(HashSet<string> made, ItemDrop item)
		{
			if ((Object)(object)item != (Object)null)
			{
				made.Add(item.m_itemData.m_shared.m_name);
			}
		}

		internal static int MoveInto(Container chest, ItemData item, Inventory from, HashSet<Container> touched)
		{
			Inventory inventory = chest.GetInventory();
			if (Plugin.DontFillEmptyChests.Value && inventory.NrOfItems() == 0)
			{
				return 0;
			}
			if (!inventory.HaveEmptySlot() && inventory.FindFreeStackSpace(item.m_shared.m_name, (float)item.m_worldLevel) <= 0)
			{
				return 0;
			}
			if (!ChestFinder.EnsureOwner(chest))
			{
				return 0;
			}
			int stack = item.m_stack;
			int num;
			if (inventory.AddItem(item))
			{
				from.RemoveItem(item);
				num = stack;
			}
			else
			{
				num = stack - item.m_stack;
			}
			if (num > 0)
			{
				touched.Add(chest);
			}
			return num;
		}

		public static void Sort(Inventory inv)
		{
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ff: Unknown result type (might be due to invalid IL or missing references)
			List<ItemData> allItems = inv.GetAllItems();
			List<ItemData> list = (from i in allItems
				orderby TypeRank(i.m_shared.m_itemType), ItemGroups.Rank(i), Localization.instance.Localize(i.m_shared.m_name), i.m_quality descending, i.m_stack descending
				select i).ToList();
			List<ItemData> list2 = new List<ItemData>();
			foreach (ItemData item in list)
			{
				if (item.m_shared.m_maxStackSize > 1)
				{
					foreach (ItemData item2 in list2)
					{
						if (item.m_stack <= 0)
						{
							break;
						}
						if (item2.IsSameType(item) && item2.m_quality == item.m_quality && item2.m_cheated == item.m_cheated && item2.m_stack < item2.m_shared.m_maxStackSize)
						{
							int num = Mathf.Min(item2.m_shared.m_maxStackSize - item2.m_stack, item.m_stack);
							item2.m_stack += num;
							item.m_stack -= num;
						}
					}
				}
				if (item.m_stack > 0)
				{
					list2.Add(item);
				}
			}
			int width = inv.GetWidth();
			for (int num2 = 0; num2 < list2.Count; num2++)
			{
				list2[num2].m_gridPos = new Vector2i(num2 % width, num2 / width);
			}
			allItems.Clear();
			allItems.AddRange(list2);
			inv.Changed(false, false);
		}

		private static int TypeRank(ItemType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Invalid comparison between Unknown and I4
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Invalid comparison between Unknown and I4
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Invalid comparison between Unknown and I4
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Expected I4, but got Unknown
			if ((int)type <= 9)
			{
				if ((int)type == 1)
				{
					return 0;
				}
				if ((int)type == 2)
				{
					return 3;
				}
				if ((int)type == 9)
				{
					goto IL_0039;
				}
			}
			else if ((int)type <= 16)
			{
				if ((int)type == 13)
				{
					return 1;
				}
				if ((int)type == 16)
				{
					return 5;
				}
			}
			else
			{
				if ((int)type == 21)
				{
					return 2;
				}
				if ((int)type == 23)
				{
					goto IL_0039;
				}
			}
			return 10 + type;
			IL_0039:
			return 4;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "OnStackAll")]
	internal static class InventoryGui_OnStackAll_Patch
	{
		private static bool Prefix(InventoryGui __instance)
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)__instance.m_currentContainer == (Object)null || ChestFinder.IsDisposal(__instance.m_currentContainer))
			{
				return true;
			}
			if (!Plugin.StackToNearby.Value || ((Character)Player.m_localPlayer).IsTeleporting())
			{
				return true;
			}
			__instance.SetupDragItem((ItemData)null, (Inventory)null, 1);
			Stacker.StackFromPlayer(__instance.m_currentContainer);
			return false;
		}
	}
	[HarmonyPatch(typeof(Container), "RPC_StackResponse")]
	internal static class Container_RPC_StackResponse_Patch
	{
		private static bool Prefix(Container __instance, bool granted)
		{
			if (!granted || (Object)(object)Player.m_localPlayer == (Object)null || ChestFinder.IsDisposal(__instance))
			{
				return true;
			}
			if (!Plugin.StackToNearby.Value)
			{
				return true;
			}
			Stacker.StackFromPlayer(__instance);
			return false;
		}
	}
	internal static class StationScope
	{
		internal static void Enter(Humanoid user, ref bool entered)
		{
			entered = Plugin.StationsFromChests.Value && (Object)(object)user != (Object)null && (Object)(object)user == (Object)(object)Player.m_localPlayer;
			if (entered)
			{
				Scope.Station++;
				Scope.Consume++;
			}
		}

		internal static void Enter(Switch sw, Humanoid user, ref bool entered)
		{
			if (IsAddFoodSwitch(sw))
			{
				entered = false;
			}
			else
			{
				Enter(user, ref entered);
			}
		}

		private static bool IsAddFoodSwitch(Switch sw)
		{
			if ((Object)(object)sw == (Object)null)
			{
				return false;
			}
			CookingStation val = ((Component)sw).GetComponentInParent<CookingStation>();
			if ((Object)(object)val == (Object)null)
			{
				val = ((Component)((Component)sw).transform.root).GetComponentInChildren<CookingStation>(true);
			}
			if ((Object)(object)val != (Object)null)
			{
				return (Object)(object)val.m_addFoodSwitch == (Object)(object)sw;
			}
			return false;
		}

		internal static void Leave(bool entered)
		{
			if (entered)
			{
				Scope.Station--;
				Scope.Consume--;
			}
		}
	}
	[HarmonyPatch(typeof(Switch), "Interact")]
	internal static class Switch_Interact_Patch
	{
		private static void Prefix(Switch __instance, Humanoid character, ref bool __state)
		{
			StationScope.Enter(__instance, character, ref __state);
		}

		private static void Finalizer(bool __state)
		{
			StationScope.Leave(__state);
		}
	}
	[HarmonyPatch(typeof(Switch), "UseItem")]
	internal static class Switch_UseItem_Patch
	{
		private static void Prefix(Switch __instance, Humanoid user, ref bool __state)
		{
			StationScope.Enter(__instance, user, ref __state);
		}

		private static void Finalizer(bool __state)
		{
			StationScope.Leave(__state);
		}
	}
	[HarmonyPatch(typeof(Fireplace), "Interact")]
	internal static class Fireplace_Interact_Patch
	{
		private static void Prefix(Humanoid user, ref bool __state)
		{
			StationScope.Enter(user, ref __state);
		}

		private static void Finalizer(bool __state)
		{
			StationScope.Leave(__state);
		}
	}
	[HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[]
	{
		typeof(ItemData),
		typeof(int)
	})]
	internal static class Inventory_RemoveItemInstance_Patch
	{
		private static bool Prefix(Inventory __instance, ItemData item, int amount, ref bool __result)
		{
			if (!StationItems.TryRemoveFromChest(__instance, item, amount))
			{
				return true;
			}
			__result = true;
			return false;
		}
	}
	[HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[] { typeof(ItemData) })]
	internal static class Inventory_RemoveWholeItemInstance_Patch
	{
		private static bool Prefix(Inventory __instance, ItemData item, ref bool __result)
		{
			if (!StationItems.TryRemoveFromChest(__instance, item, item?.m_stack ?? 0))
			{
				return true;
			}
			__result = true;
			return false;
		}
	}
	[HarmonyPatch(typeof(Inventory), "RemoveOneItem")]
	internal static class Inventory_RemoveOneItem_Patch
	{
		private static bool Prefix(Inventory __instance, ItemData item, ref bool __result)
		{
			if (!StationItems.TryRemoveFromChest(__instance, item, 1))
			{
				return true;
			}
			__result = true;
			return false;
		}
	}
	internal static class StationItems
	{
		internal static bool TryRemoveFromChest(Inventory inv, ItemData item, int amount)
		{
			if (item == null || amount <= 0 || Scope.Station <= 0)
			{
				return false;
			}
			if (!Scope.IsLocalPlayerInventory(inv, out var player) || inv.ContainsItem(item))
			{
				return false;
			}
			foreach (Container item2 in ChestFinder.GetNearby(player, Plugin.CraftingRange.Value))
			{
				Inventory inventory = item2.GetInventory();
				if (inventory.ContainsItem(item) && ChestFinder.EnsureOwner(item2))
				{
					inventory.RemoveItem(item, amount);
					ChestFinder.InvalidateCounts();
					return true;
				}
			}
			return false;
		}
	}
	internal static class Tidier
	{
		internal const string Junk = "\u0001junk";

		public static void TidyChest(Container opened)
		{
			TidyChest(opened, showMessage: true);
		}

		internal static bool TidyChest(Container opened, bool showMessage, List<Container> nearby = null)
		{
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_0227: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)opened == (Object)null || ChestFinder.IsDisposal(opened) || !ChestFinder.CanUseOpened(opened, localPlayer) || !ChestFinder.EnsureOwner(opened))
			{
				return false;
			}
			ItemGroups.ReloadIfChanged();
			if (nearby == null)
			{
				ChestFinder.Invalidate();
				nearby = ChestFinder.GetNearby(localPlayer, Plugin.StackingRange.Value);
			}
			List<Container> list = nearby.Where((Container c) => (Object)(object)c != (Object)(object)opened).ToList();
			Inventory inventory = opened.GetInventory();
			HashSet<Container> hashSet = new HashSet<Container>();
			int num = 0;
			int num2 = MergeIntoStrongerHome(inventory, list, hashSet);
			string text = Category(inventory);
			if (text != null)
			{
				foreach (ItemData item in inventory.GetAllItems().ToList())
				{
					string text2 = GroupOf(item);
					if (text2 == text)
					{
						continue;
					}
					num2 += MoveToGroupChest(item, text2, inventory, list, hashSet);
					if (inventory.ContainsItem(item))
					{
						num2 += MoveToEmptyChest(item, inventory, list, hashSet);
					}
					if (inventory.ContainsItem(item) && (!Plugin.ShareChests.Value || GroupsIn(inventory).Count > 2))
					{
						if (Plugin.ShareChests.Value)
						{
							num2 += MoveToSharedChest(item, text2, inventory, list, hashSet);
						}
						if (text2 != "\u0001junk" && text != "\u0001junk" && inventory.ContainsItem(item))
						{
							num2 += MoveToJunkChest(item, inventory, list, hashSet);
						}
						if (inventory.ContainsItem(item))
						{
							num++;
						}
					}
				}
			}
			int num3 = (Plugin.TidyGathers.Value ? Gather(opened, text, list, hashSet) : 0);
			inventory.Changed(false, false);
			Stacker.Sort(inventory);
			foreach (Container item2 in hashSet)
			{
				Stacker.Sort(item2.GetInventory());
				InventoryGui.instance.m_moveItemEffects.Create(((Component)item2).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID));
			}
			if (!showMessage)
			{
				return true;
			}
			List<string> list2 = new List<string>();
			if (num2 > 0)
			{
				list2.Add($"sent out {num2}");
			}
			if (num3 > 0)
			{
				list2.Add($"brought in {num3}");
			}
			string text3 = ((list2.Count > 0) ? ("Tidied: " + string.Join(", ", list2) + " items") : ((num <= 0) ? "Sorted - everything here belongs" : "Sorted - nothing had a better chest to go to"));
			if (list2.Count > 0 && num > 0)
			{
				text3 += string.Format("\n{0} {1} had nowhere else to go, so stayed", num, (num == 1) ? "stack" : "stacks");
			}
			((Character)localPlayer).Message((MessageType)2, text3, 0, (Sprite)null, false);
			return true;
		}

		private static int Gather(Container opened, string category, List<Container> others, HashSet<Container> touched)
		{
			if (category == null || category == "\u0001junk")
			{
				return 0;
			}
			Inventory inventory = opened.GetInventory();
			int num = 0;
			foreach (Container other in others)
			{
				Inventory inventory2 = other.GetInventory();
				if ((Category(inventory2) == category && CategoryCount(inventory2, category) > CategoryCount(inventory, category)) || !ChestFinder.EnsureOwner(other))
				{
					continue;
				}
				bool flag = false;
				foreach (ItemData item in inventory2.GetAllItems().ToList())
				{
					if (!(GroupOf(item) != category))
					{
						int num2 = Stacker.MoveInto(opened, item, inventory2, touched);
						if (num2 > 0)
						{
							flag = true;
						}
						num += num2;
					}
				}
				if (flag)
				{
					touched.Add(other);
					inventory2.Changed(false, false);
				}
			}
			return num;
		}

		private static int MergeIntoStrongerHome(Inventory from, List<Container> others, HashSet<Container> touched)
		{
			string own = Category(from);
			if (own == null || own == "\u0001junk")
			{
				return 0;
			}
			int mine = CategoryCount(from, own);
			List<Container> list = (from c in others
				select new
				{
					Chest = c,
					Inv = c.GetInventory()
				} into x
				where Category(x.Inv) == own && CategoryCount(x.Inv, own) > mine
				orderby CategoryCount(x.Inv, own) descending
				select x.Chest).ToList();
			if (list.Count == 0)
			{
				return 0;
			}
			int num = 0;
			foreach (ItemData item in from.GetAllItems().ToList())
			{
				if (GroupOf(item) == own)
				{
					num += MoveIntoFirst(item, from, list, touched);
				}
			}
			return num;
		}

		internal static string GroupOf(ItemData item)
		{
			return ItemGroups.Of(item) ?? "\u0001junk";
		}

		private static int MoveToGroupChest(ItemData item, string group, Inventory from, List<Container> others, HashSet<Container> touched)
		{
			List<Container> homes = (from c in others
				select new
				{
					Chest = c,
					Inv = c.GetInventory()
				} into x
				where Category(x.Inv) == @group
				orderby CategoryCount(x.Inv, @group) descending
				select x.Chest).ToList();
			if (Plugin.ShareChests.Value)
			{
				homes.AddRange(others.Where(delegate(Container c)
				{
					if (!homes.Contains(c))
					{
						HashSet<string> hashSet = GroupsIn(c.GetInventory());
						if (hashSet.Count == 2)
						{
							return hashSet.Contains(group);
						}
					}
					return false;
				}));
			}
			return MoveIntoFirst(item, from, homes, touched);
		}

		internal static int MoveToEmptyChest(ItemData item, Inventory from, IEnumerable<Container> candidates, HashSet<Container> touched)
		{
			return MoveIntoFirst(item, from, candidates.Whe