Decompiled source of Nearby Chests v1.0.9

plugins/NearbyChests.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using 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.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 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);
		}

		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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: 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_00e5: 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;
			}
			Vector3 val = ((Component)c).transform.position - playerPos;
			if (((Vector3)(ref val)).sqrMagnitude > rangeSq)
			{
				return false;
			}
			if ((Object)(object)((Component)c).GetComponentInParent<Incinerator>() != (Object)null || (Object)(object)((Component)c).GetComponentInParent<TombStone>() != (Object)null)
			{
				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 EnsureOwner(Container c)
		{
			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 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 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 Finalizer(bool craft)
		{
			if (craft)
			{
				Scope.Craft--;
			}
			else
			{
				Scope.Build--;
			}
		}
	}
	[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.Consume <= 0 || !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 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(new char[1] { '\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(new char[1] { ',' });
				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(new char[1] { '*' }), 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.0.9")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "NearbyChests";

		public const string ModName = "Nearby Chests";

		public const string Version = "1.0.9";

		internal static ManualLogSource Log;

		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> 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<string> UnassignedItemTypes;

		internal static ConfigEntry<bool> FallbackToOpenChest;

		internal static ConfigEntry<bool> SortAfterStack;

		internal static ConfigEntry<bool> TidyButton;

		internal static ConfigEntry<bool> TidyGathers;

		internal static ConfigEntry<bool> ShareChests;

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

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Expected O, but got Unknown
			//IL_033d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0347: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			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.");
			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>()));
			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.");
			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.");
			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.");
			ParseUnassignedTypes();
			ItemGroups.Load();
			UnassignedItemTypes.SettingChanged += delegate
			{
				ParseUnassignedTypes();
			};
			CraftingRange.SettingChanged += delegate
			{
				ChestFinder.Invalidate();
			};
			StackingRange.SettingChanged += delegate
			{
				ChestFinder.Invalidate();
			};
			IncludeCartsAndShips.SettingChanged += delegate
			{
				ChestFinder.Invalidate();
			};
			_harmony = new Harmony("NearbyChests");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Nearby Chests 1.0.9 loaded");
		}

		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)
		{
			//IL_0204: 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_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: 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)
			{
				return;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			ChestFinder.Invalidate();
			List<Container> list = new List<Container> { 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));
			}
			string text = ((num2 > 0) ? $"\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);
				Game.instance.IncrementPlayerStat((PlayerStatType)82, 1f, false);
			}
			else
			{
				((Character)localPlayer).Message((MessageType)2, (text.Length > 0) ? text.TrimStart(new char[1] { '\n' }) : "$msg_stackall_none", 0, (Sprite)null, false);
			}
		}

		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 (Plugin.FallbackToOpenChest.Value)
			{
				num += MoveInto(opened, item, from, touched);
			}
			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_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: 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.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 (!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 (!Plugin.StackToNearby.Value || (Object)(object)Player.m_localPlayer == (Object)null || ((Character)Player.m_localPlayer).IsTeleporting() || (Object)(object)__instance.m_currentContainer == (Object)null)
			{
				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 (!Plugin.StackToNearby.Value || !granted || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				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)
		{
			//IL_0200: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: 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.EnsureOwner(opened))
			{
				return;
			}
			ItemGroups.ReloadIfChanged();
			ChestFinder.Invalidate();
			List<Container> list = (from c in ChestFinder.GetNearby(localPlayer, Plugin.StackingRange.Value)
				where (Object)(object)c != (Object)(object)opened
				select c).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));
			}
			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);
		}

		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.Where((Container c) => c.GetInventory().NrOfItems() == 0).ToList(), touched);
		}

		internal static int MoveToSharedChest(ItemData item, string group, Inventory from, IEnumerable<Container> candidates, HashSet<Container> touched)
		{
			if (group == "\u0001junk")
			{
				return 0;
			}
			List<Container> chests = (from c in candidates
				select new
				{
					Chest = c,
					Inv = c.GetInventory()
				} into x
				select new
				{
					Chest = x.Chest,
					Inv = x.Inv,
					Groups = GroupsIn(x.Inv)
				} into x
				where x.Groups.Count == 1 && !x.Groups.Contains("\u0001junk") && !x.Groups.Contains(@group) && x.Inv.HaveEmptySlot()
				orderby Relatedness(@group, x.Groups.First()), x.Inv.GetEmptySlots() descending
				select x.Chest).ToList();
			return MoveIntoFirst(item, from, chests, touched);
		}

		private static int MoveToJunkChest(ItemData item, Inventory from, List<Container> others, HashSet<Container> touched)
		{
			List<Container> chests = (from c in others
				where Category(c.GetInventory()) == "\u0001junk"
				orderby CategoryCount(c.GetInventory(), "\u0001junk") descending
				select c).ToList();
			return MoveIntoFirst(item, from, chests, touched);
		}

		private static int MoveIntoFirst(ItemData item, Inventory from, List<Container> chests, HashSet<Container> touched)
		{
			int num = 0;
			foreach (Container chest in chests)
			{
				num += Stacker.MoveInto(chest, item, from, touched);
				if (!from.ContainsItem(item))
				{
					break;
				}
			}
			return num;
		}

		private static int Relatedness(string a, string b)
		{
			int num = ItemGroups.RankOf(a);
			int num2 = ItemGroups.RankOf(b);
			if (num == int.MaxValue || num2 == int.MaxValue)
			{
				return 1000;
			}
			return Math.Abs(num - num2);
		}

		internal static HashSet<string> GroupsIn(Inventory inv)
		{
			HashSet<string> hashSet = new HashSet<string>();
			foreach (ItemData allItem in inv.GetAllItems())
			{
				hashSet.Add(GroupOf(allItem));
			}
			return hashSet;
		}

		internal static string Category(Inventory inv)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (ItemData allItem in inv.GetAllItems())
			{
				string key = GroupOf(allItem);
				dictionary.TryGetValue(key, out var value);
				dictionary[key] = value + 1;
			}
			if (dictionary.Count == 0)
			{
				return null;
			}
			return (from kv in dictionary
				orderby kv.Value descending, (!(kv.Key == "\u0001junk")) ? ItemGroups.RankOf(kv.Key) : int.MaxValue
				select kv).First().Key;
		}

		private static int CategoryCount(Inventory inv, string group)
		{
			return inv.GetAllItems().Count((ItemData i) => GroupOf(i) == group);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "Awake")]
	internal static class InventoryGui_Awake_TidyButton_Patch
	{
		private static void Postfix(InventoryGui __instance)
		{
			//IL_00c1: 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_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Expected O, but got Unknown
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Expected O, but got Unknown
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Expected O, but got Unknown
			if (!Plugin.TidyButton.Value)
			{
				return;
			}
			Button stackAllButton = __instance.m_stackAllButton;
			if ((Object)(object)stackAllButton == (Object)null)
			{
				return;
			}
			GameObject val = Object.Instantiate<GameObject>(((Component)stackAllButton).gameObject, ((Component)stackAllButton).transform.parent);
			((Object)val).name = "NearbyChests_Tidy";
			UIGamePad[] componentsInChildren = val.GetComponentsInChildren<UIGamePad>(true);
			foreach (UIGamePad val2 in componentsInChildren)
			{
				if ((Object)(object)val2.m_hint != (Object)null)
				{
					Object.Destroy((Object)(object)val2.m_hint);
				}
				Object.Destroy((Object)(object)val2);
			}
			Color color = default(Color);
			((Color)(ref color))..ctor(1f, 0.85f, 0.55f);
			TMP_Text[] componentsInChildren2 = val.GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text obj in componentsInChildren2)
			{
				color = ((Graphic)obj).color;
				((Component)obj).gameObject.SetActive(false);
			}
			RectTransform val3 = (RectTransform)val.transform;
			Rect rect = ((RectTransform)((Component)stackAllButton).transform).rect;
			float height = ((Rect)(ref rect)).height;
			val3.pivot = new Vector2(0.5f, 0.5f);
			val3.SetSizeWithCurrentAnchors((Axis)0, height);
			val3.SetSizeWithCurrentAnchors((Axis)1, height);
			GameObject val4 = new GameObject("Icon", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			});
			RectTransform val5 = (RectTransform)val4.transform;
			((Transform)val5).SetParent((Transform)(object)val3, false);
			val5.anchorMin = new Vector2(0.2f, 0.2f);
			val5.anchorMax = new Vector2(0.8f, 0.8f);
			Vector2 offsetMin = (val5.offsetMax = Vector2.zero);
			val5.offsetMin = offsetMin;
			Image component = val4.GetComponent<Image>();
			component.sprite = TidyIcon.Create();
			((Graphic)component).color = color;
			component.preserveAspect = true;
			((Graphic)component).raycastTarget = false;
			TidyButtonPlacement.Button = val3;
			TidyButtonPlacement.Placed = false;
			Button component2 = val.GetComponent<Button>();
			component2.onClick = new ButtonClickedEvent();
			((UnityEvent)component2.onClick).AddListener((UnityAction)delegate
			{
				Player localPlayer = Player.m_localPlayer;
				if (!((Object)(object)localPlayer == (Object)null) && !((Character)localPlayer).IsTeleporting() && !((Object)(object)__instance.m_currentContainer == (Object)null))
				{
					__instance.SetupDragItem((ItemData)null, (Inventory)null, 1);
					Tidier.TidyChest(__instance.m_currentContainer);
				}
			});
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateContainer")]
	internal static class TidyButtonPlacement
	{
		internal static RectTransform Button;

		internal static bool Placed;

		private static readonly Vector3[] StackCorners = (Vector3[])(object)new Vector3[4];

		private static readonly Vector3[] ButtonCorners = (Vector3[])(object)new Vector3[4];

		private static void Postfix(InventoryGui __instance)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			if (!Placed && !((Object)(object)Button == (Object)null) && !((Object)(object)__instance.m_container == (Object)null) && ((Component)__instance.m_container).gameObject.activeInHierarchy)
			{
				RectTransform val = (RectTransform)((Component)__instance.m_stackAllButton).transform;
				Canvas.ForceUpdateCanvases();
				val.GetWorldCorners(StackCorners);
				Button.GetWorldCorners(ButtonCorners);
				float num = ButtonCorners[2].x - ButtonCorners[1].x;
				if (!(num <= 0f))
				{
					float num2 = num * 0.15f;
					float num3 = StackCorners[0].x - num2 - num / 2f;
					float num4 = (StackCorners[0].y + StackCorners[1].y) / 2f;
					((Transform)Button).position = new Vector3(num3, num4, ((Transform)val).position.z);
					Placed = true;
				}
			}
		}
	}
	internal static class TidyIcon
	{
		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_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: 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)
			//IL_00e0: 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 val2 = default(Color);
			((Color)(ref val2))..ctor(1f, 1f, 1f, 0f);
			Color[] array = (Color[])(object)new Color[1024];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = val2;
			}
			int[] array2 = new int[3] { 28, 20, 12 };
			int num = 5;
			int[] array3 = new int[3] { 26, 16, 6 };
			for (int j = 0; j < array2.Length; j++)
			{
				for (int k = array3[j]; k < array3[j] + num; k++)
				{
					for (int l = 2; l < 2 + array2[j]; l++)
					{
						float num2 = ((l == 1 + array2[j]) ? 0.5f : 1f);
						array[k * 32 + l] = new Color(1f, 1f, 1f, num2);
					}
				}
			}
			val.SetPixels(array);
			val.Apply();
			_sprite = Sprite.Create(val, new Rect(0f, 0f, 32f, 32f), new Vector2(0.5f, 0.5f));
			return _sprite;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}