Decompiled source of ChestButler v2.1.1

ChestButler.dll

Decompiled 16 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ChestButler.Core;
using HarmonyLib;
using Jotunn.Configs;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using MultiUserChest;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("ChestButler")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.1.1.0")]
[assembly: AssemblyInformationalVersion("2.1.1")]
[assembly: AssemblyProduct("ChestButler")]
[assembly: AssemblyTitle("ChestButler")]
[assembly: AssemblyVersion("2.1.1.0")]
namespace ChestButler
{
	[BepInPlugin("eksolutions.chestbutler", "ChestButler", "2.1.1")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string ModGuid = "eksolutions.chestbutler";

		public const string ModName = "ChestButler";

		public const string ModVersion = "2.1.1";

		internal static Plugin Instance;

		internal static ManualLogSource Log;

		internal static ConfigEntry<float> SorterRadius;

		internal static ConfigEntry<float> TransferInterval;

		internal static ConfigEntry<int> StacksPerTick;

		internal static ConfigEntry<bool> ContainsFallback;

		internal static ConfigEntry<bool> VehiclesAreStorage;

		private Harmony _harmony;

		internal static ConfigEntry<float> StationRange => OrganizeConfig.StationRange;

		private void Awake()
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected O, but got Unknown
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Expected O, but got Unknown
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Expected O, but got Unknown
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Expected O, but got Unknown
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Expected O, but got Unknown
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Expected O, but got Unknown
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			SorterRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Sorting", "Radius", 128f, new ConfigDescription("Radius (m) around a sorter chest in which target chests are searched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 128f), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			TransferInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Sorting", "TransferInterval", 1f, new ConfigDescription("Seconds between transfer ticks per sorter. Client-side: raise it if the mod costs you frames.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>()));
			StacksPerTick = ((BaseUnityPlugin)this).Config.Bind<int>("Sorting", "StacksPerTick", 2, new ConfigDescription("How many item stacks a sorter moves per tick. Client-side.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>()));
			ContainsFallback = ((BaseUnityPlugin)this).Config.Bind<bool>("Sorting", "ContainsFallback", true, new ConfigDescription("Route items to chests that already contain them when no explicit filter matches.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			VehiclesAreStorage = ((BaseUnityPlugin)this).Config.Bind<bool>("Sorting", "VehiclesAreStorage", false, new ConfigDescription("Treat cart and ship inventories as storage. Off (default): the sorter, Organize, Pull and Gather all ignore vehicles entirely - they are transport, and their own Pin/Pull buttons still work for manual loading.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			Groups.Init(((BaseUnityPlugin)this).Config);
			Stations.Init(((BaseUnityPlugin)this).Config);
			OrganizeConfig.Init(((BaseUnityPlugin)this).Config);
			Gather.Init(((BaseUnityPlugin)this).Config);
			SorterChestPiece.Register();
			_harmony = new Harmony("eksolutions.chestbutler");
			_harmony.PatchAll();
			Log.LogInfo((object)"ChestButler 2.1.1 loaded");
		}
	}
}
namespace ChestButler.Patches
{
	[HarmonyPatch(typeof(Hud))]
	internal static class BuildGatherPatch
	{
		internal static bool InBuildInfo { get; private set; }

		[HarmonyPrefix]
		[HarmonyPatch("SetupPieceInfo")]
		private static void SetupPieceInfoPrefix()
		{
			InBuildInfo = true;
		}

		[HarmonyPostfix]
		[HarmonyPatch("SetupPieceInfo")]
		private static void SetupPieceInfoPostfix()
		{
			InBuildInfo = false;
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class Container_Awake_Patch
	{
		private static void Postfix(Container __instance)
		{
			ContainerTracker.Register(__instance);
			if ((Object)(object)((Component)__instance).GetComponent<SorterBehaviour>() == (Object)null && (Object)(object)((Component)__instance).GetComponentInParent<Piece>() != (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<SorterBehaviour>();
			}
		}
	}
	[HarmonyPatch(typeof(Container), "OnDestroyed")]
	internal static class Container_OnDestroyed_Patch
	{
		private static void Prefix(Container __instance)
		{
			ContainerTracker.Unregister(__instance);
		}
	}
	[HarmonyPatch(typeof(InventoryGui))]
	internal static class GatherPatch
	{
		private static Button _gatherBtn;

		private static TMP_Text _gatherLabel;

		private static readonly List<GatherNeed> Needs = new List<GatherNeed>();

		private static readonly HashSet<string> Seen = new HashSet<string>();

		private static bool _onlyOneIngredient;

		private static List<Container> _sources;

		private const int MaxNeeds = 16;

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

		private static readonly MethodInfo RecipeGetter = ((SelectedRecipeField != null) ? AccessTools.PropertyGetter(SelectedRecipeField.FieldType, "Recipe") : null);

		private static bool _reflectionWarned;

		private static Recipe SelectedRecipe(InventoryGui gui)
		{
			if ((Object)(object)gui == (Object)null || SelectedRecipeField == null || RecipeGetter == null)
			{
				if (!_reflectionWarned)
				{
					_reflectionWarned = true;
					Plugin.Log.LogWarning((object)"[gather] could not reach InventoryGui.m_selectedRecipe; require-only-one-ingredient recipes will be treated as normal ones");
				}
				return null;
			}
			try
			{
				object value = SelectedRecipeField.GetValue(gui);
				return (Recipe)((value == null) ? null : /*isinst with value type is only supported in some contexts*/);
			}
			catch (Exception ex)
			{
				if (!_reflectionWarned)
				{
					_reflectionWarned = true;
					Plugin.Log.LogWarning((object)("[gather] reading m_selectedRecipe failed: " + ex.Message));
				}
				return null;
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch("SetupRequirementList")]
		private static void SetupRequirementListPrefix(InventoryGui __instance)
		{
			Needs.Clear();
			Seen.Clear();
			_sources = null;
			Recipe val = SelectedRecipe(__instance);
			_onlyOneIngredient = (Object)(object)val != (Object)null && val.m_requireOnlyOneIngredient;
		}

		[HarmonyPostfix]
		[HarmonyPatch("SetupRequirement")]
		private static void SetupRequirementPostfix(Transform elementRoot, Requirement req, Player player, bool craft, int quality, int craftMultiplier, bool __result)
		{
			if (!__result || req?.m_resItem?.m_itemData?.m_shared == null || (Object)(object)player == (Object)null)
			{
				return;
			}
			string name = req.m_resItem.m_itemData.m_shared.m_name;
			if (string.IsNullOrEmpty(name))
			{
				return;
			}
			int amount = req.GetAmount(quality);
			if (amount <= 0)
			{
				return;
			}
			int needed = amount * Mathf.Max(1, craftMultiplier);
			Inventory inventory = ((Humanoid)player).GetInventory();
			int inPlayer = ((inventory != null) ? inventory.CountItems(name, -1, true) : 0);
			bool inBuildInfo = BuildGatherPatch.InBuildInfo;
			List<Container> sources;
			if (inBuildInfo)
			{
				sources = BuildGather.SourcesCached();
			}
			else
			{
				if (_sources == null)
				{
					_sources = Gatherer.Sources();
				}
				sources = _sources;
			}
			int num = Gatherer.CountInStorage(sources, name);
			if (!inBuildInfo && Seen.Add(name) && Needs.Count < 16)
			{
				Needs.Add(new GatherNeed
				{
					SharedName = name,
					Display = Names.Normalize(name),
					Needed = needed,
					InPlayer = inPlayer,
					InStorage = num
				});
			}
			if (Gather.CountsShown && num > 0)
			{
				Annotate(elementRoot, num);
			}
		}

		private static void Annotate(Transform elementRoot, int inStorage)
		{
			if ((Object)(object)elementRoot == (Object)null)
			{
				return;
			}
			TMP_Text val = null;
			Transform val2 = elementRoot.Find("res_amount");
			if ((Object)(object)val2 != (Object)null)
			{
				val = ((Component)val2).GetComponent<TMP_Text>();
			}
			if ((Object)(object)val == (Object)null)
			{
				TMP_Text[] componentsInChildren = ((Component)elementRoot).GetComponentsInChildren<TMP_Text>(true);
				if (componentsInChildren != null && componentsInChildren.Length != 0)
				{
					val = componentsInChildren[^1];
				}
			}
			if (!((Object)(object)val == (Object)null) && (val.text == null || !val.text.Contains("  <color=#9BE07A>")))
			{
				val.textWrappingMode = (TextWrappingModes)0;
				val.overflowMode = (TextOverflowModes)0;
				TMP_Text obj = val;
				obj.text = obj.text + "  <color=#9BE07A><size=85%>(" + inStorage + ")</size></color>";
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("Show")]
		private static void ShowPostfix(InventoryGui __instance)
		{
			EnsureButton(__instance);
		}

		[HarmonyPostfix]
		[HarmonyPatch("UpdateRecipe")]
		private static void UpdateRecipePostfix(InventoryGui __instance)
		{
			EnsureButton(__instance);
			RefreshButton();
		}

		[HarmonyPostfix]
		[HarmonyPatch("Hide")]
		private static void HidePostfix()
		{
			if ((Object)(object)_gatherBtn != (Object)null)
			{
				((Component)_gatherBtn).gameObject.SetActive(false);
			}
		}

		private static void EnsureButton(InventoryGui gui)
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Expected O, but got Unknown
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Expected O, but got Unknown
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: 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_0119: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_gatherBtn != (Object)null || (Object)(object)gui == (Object)null)
			{
				return;
			}
			Button craftButton = gui.m_craftButton;
			if ((Object)(object)craftButton == (Object)null)
			{
				return;
			}
			RectTransform component = ((Component)craftButton).GetComponent<RectTransform>();
			if (!((Object)(object)component == (Object)null) && !((Object)(object)((Transform)component).parent == (Object)null))
			{
				Button val = Object.Instantiate<Button>(craftButton, ((Transform)component).parent);
				((Object)val).name = "psort_gather";
				Localize componentInChildren = ((Component)val).GetComponentInChildren<Localize>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					Object.DestroyImmediate((Object)(object)componentInChildren);
				}
				UIGamePad[] componentsInChildren = ((Component)val).GetComponentsInChildren<UIGamePad>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					Object.DestroyImmediate((Object)(object)componentsInChildren[i]);
				}
				val.onClick = new ButtonClickedEvent();
				((UnityEvent)val.onClick).AddListener(new UnityAction(OnGatherClick));
				RectTransform component2 = ((Component)val).GetComponent<RectTransform>();
				component2.anchorMin = component.anchorMin;
				component2.anchorMax = component.anchorMax;
				component2.pivot = component.pivot;
				component2.sizeDelta = component.sizeDelta;
				Vector2 anchoredPosition = component.anchoredPosition;
				Rect rect = component.rect;
				component2.anchoredPosition = anchoredPosition - new Vector2(0f, ((Rect)(ref rect)).height + 6f);
				_gatherLabel = ((Component)val).GetComponentInChildren<TMP_Text>();
				if ((Object)(object)_gatherLabel != (Object)null)
				{
					float fontSize = _gatherLabel.fontSize;
					_gatherLabel.enableAutoSizing = true;
					_gatherLabel.fontSizeMax = fontSize;
					_gatherLabel.fontSizeMin = fontSize - 4f;
					_gatherLabel.text = "Gather";
				}
				_gatherBtn = val;
			}
		}

		private static void RefreshButton()
		{
			if ((Object)(object)_gatherBtn == (Object)null)
			{
				return;
			}
			if (!Gather.IsEnabled)
			{
				((Component)_gatherBtn).gameObject.SetActive(false);
				return;
			}
			List<GatherNeed> list = GatherMath.Resolve(Needs, _onlyOneIngredient);
			int num = 0;
			foreach (GatherNeed item in list)
			{
				num += item.Gatherable;
			}
			bool flag = num > 0;
			((Component)_gatherBtn).gameObject.SetActive(Needs.Count > 0);
			((Selectable)_gatherBtn).interactable = flag;
			if ((Object)(object)_gatherLabel != (Object)null)
			{
				_gatherLabel.text = (flag ? ("Gather (" + num + ")") : "Gather");
			}
			InventoryGui instance = InventoryGui.instance;
			if ((Object)(object)instance != (Object)null && (Object)(object)instance.m_craftButton != (Object)null && ((Component)_gatherBtn).gameObject.activeInHierarchy)
			{
				GamepadNav.LinkVertical((Selectable)(object)instance.m_craftButton, (Selectable)(object)_gatherBtn, onlyIfVanillaEmpty: false);
			}
		}

		private static void OnGatherClick()
		{
			if (Gather.IsEnabled && !((Object)(object)Player.m_localPlayer == (Object)null))
			{
				List<GatherNeed> list = GatherMath.Resolve(Needs, _onlyOneIngredient);
				if (list.Count == 0)
				{
					Msg((Needs.Count == 0) ? "Select a recipe first" : "Nothing to gather - nearby chests have none of what this needs");
					return;
				}
				Gatherer.Pull(list, out var movedTotal, out var typesMoved);
				Msg((movedTotal > 0) ? ("Gathered " + movedTotal + " item" + ((movedTotal == 1) ? "" : "s") + " (" + typesMoved + " type" + ((typesMoved == 1) ? "" : "s") + ")") : "Nothing could be gathered - your inventory may be full");
			}
		}

		private static void Msg(string text)
		{
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false);
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui))]
	internal static class GuiPatch
	{
		private static RectTransform _bar;

		private static Button _sorterBtn;

		private static Button _pinBtn;

		private static Button _clearBtn;

		private static Button _pullBtn;

		private static Button _organizeBtn;

		private static TMP_Text _sorterLabel;

		private static TMP_Text _pinLabel;

		private static TMP_Text _clearLabel;

		private static TMP_Text _pullLabel;

		private static TMP_Text _organizeLabel;

		private static Container _current;

		private static readonly FieldRef<InventoryGui, InventoryGrid> ContainerGridRef = AccessTools.FieldRefAccess<InventoryGui, InventoryGrid>("m_containerGrid");

		private static readonly FieldRef<InventoryGrid, RectTransform> GridRootRef = AccessTools.FieldRefAccess<InventoryGrid, RectTransform>("m_gridRoot");

		private static float _lastBarY = float.NaN;

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

		private const float ConfirmWindow = 5f;

		private const float MinConfirmDelay = 0.3f;

		private static OrganizePlan _pendingPlan;

		private static Container _pendingChest;

		private static float _pendingAt;

		[HarmonyPostfix]
		[HarmonyPatch("Show")]
		private static void ShowPostfix(InventoryGui __instance, Container container)
		{
			if ((Object)(object)container != (Object)(object)_pendingChest)
			{
				ClearPending();
			}
			_current = container;
			_lastBarY = float.NaN;
			EnsureBar(__instance);
			PositionBar(__instance);
			Refresh();
		}

		[HarmonyPostfix]
		[HarmonyPatch("UpdateContainer")]
		private static void UpdateContainerPostfix(InventoryGui __instance)
		{
			if (!((Object)(object)_current == (Object)null) && !((Object)(object)_bar == (Object)null) && ((Component)_bar).gameObject.activeSelf)
			{
				PositionBar(__instance);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("Hide")]
		private static void HidePostfix()
		{
			_current = null;
			ClearPending();
			if ((Object)(object)_bar != (Object)null)
			{
				((Component)_bar).gameObject.SetActive(false);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("Update")]
		private static void UpdatePostfix()
		{
			if (_pendingPlan != null && Time.time - _pendingAt > 5f)
			{
				ClearPending();
			}
		}

		private static void EnsureBar(InventoryGui gui)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Expected O, but got Unknown
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Expected O, but got Unknown
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Expected O, but got Unknown
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Expected O, but got Unknown
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Expected O, but got Unknown
			if ((Object)(object)_bar != (Object)null)
			{
				return;
			}
			Button takeAllButton = gui.m_takeAllButton;
			if (!((Object)(object)takeAllButton == (Object)null))
			{
				RectTransform component = ((Component)takeAllButton).GetComponent<RectTransform>();
				Transform parent = ((Transform)component).parent;
				if (!((Object)(object)((parent is RectTransform) ? parent : null) == (Object)null))
				{
					GameObject val = new GameObject("psort_bar", new Type[1] { typeof(RectTransform) });
					_bar = (RectTransform)val.transform;
					((Transform)_bar).SetParent(((Transform)component).parent, false);
					_bar.anchorMin = new Vector2(0f, 0f);
					_bar.anchorMax = new Vector2(0f, 0f);
					_bar.pivot = new Vector2(0f, 1f);
					HorizontalLayoutGroup obj = val.AddComponent<HorizontalLayoutGroup>();
					((HorizontalOrVerticalLayoutGroup)obj).spacing = 8f;
					((LayoutGroup)obj).childAlignment = (TextAnchor)6;
					((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true;
					((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true;
					((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = false;
					((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false;
					ContentSizeFitter obj2 = val.AddComponent<ContentSizeFitter>();
					obj2.horizontalFit = (FitMode)2;
					obj2.verticalFit = (FitMode)2;
					_sorterBtn = MakeButton(takeAllButton, "psort_toggle", new UnityAction(OnSorterClick), out _sorterLabel);
					_pinBtn = MakeButton(takeAllButton, "psort_pin", new UnityAction(OnPinClick), out _pinLabel);
					_clearBtn = MakeButton(takeAllButton, "psort_clear", new UnityAction(OnClearClick), out _clearLabel);
					_pullBtn = MakeButton(takeAllButton, "psort_pull", new UnityAction(OnPullClick), out _pullLabel);
					_organizeBtn = MakeButton(takeAllButton, "psort_organize", new UnityAction(OnOrganizeClick), out _organizeLabel);
				}
			}
		}

		private unsafe static void PositionBar(InventoryGui gui)
		{
			//IL_003d: 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_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_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: 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_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			if ((Object)(object)_bar == (Object)null)
			{
				return;
			}
			Button takeAllButton = gui.m_takeAllButton;
			if ((Object)(object)takeAllButton == (Object)null)
			{
				return;
			}
			RectTransform component = ((Component)takeAllButton).GetComponent<RectTransform>();
			Transform parent = ((Transform)component).parent;
			RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Rect rect = val.rect;
			Rect val2 = component.rect;
			float width = ((Rect)(ref val2)).width;
			val2 = component.rect;
			float height = ((Rect)(ref val2)).height;
			float num = ((Rect)(ref rect)).x + ((Rect)(ref rect)).width * component.anchorMin.x;
			float num2 = ((Rect)(ref rect)).y + ((Rect)(ref rect)).height * component.anchorMin.y;
			float num3 = num + component.anchoredPosition.x + (0.5f - component.pivot.x) * width;
			float num4 = num2 + component.anchoredPosition.y + (0.5f - component.pivot.y) * height;
			float num5 = num3 - width * 0.5f - ((Rect)(ref rect)).xMin;
			float num6 = ((Rect)(ref rect)).yMax - (num4 + height * 0.5f) + height;
			if (TryGetUsedGridBottom(gui, val, out var bottomLocalY) && ((Rect)(ref rect)).yMin + num6 > bottomLocalY - 6f)
			{
				num6 = bottomLocalY - 6f - ((Rect)(ref rect)).yMin;
				float num7 = 0f - height;
				if (num6 < num7)
				{
					num6 = num7;
				}
			}
			if (float.IsNaN(_lastBarY) || Mathf.Abs(num6 - _lastBarY) > 0.5f)
			{
				_bar.anchoredPosition = new Vector2(num5, num6);
				_lastBarY = num6;
				ManualLogSource log = Plugin.Log;
				string[] obj = new string[8] { "[ui] panel=", null, null, null, null, null, null, null };
				val2 = rect;
				obj[1] = ((object)(*(Rect*)(&val2))/*cast due to .constrained prefix*/).ToString();
				obj[2] = " usedGridBottom=";
				obj[3] = bottomLocalY.ToString();
				obj[4] = " barY=";
				obj[5] = num6.ToString();
				obj[6] = " leftMargin=";
				obj[7] = num5.ToString();
				log.LogDebug((object)string.Concat(obj));
			}
		}

		private static bool TryGetUsedGridBottom(InventoryGui gui, RectTransform space, out float bottomLocalY)
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			bottomLocalY = 0f;
			InventoryGrid val = ContainerGridRef.Invoke(gui);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			RectTransform val2 = GridRootRef.Invoke(val);
			if ((Object)(object)val2 == (Object)null || ((Transform)val2).childCount == 0)
			{
				return false;
			}
			float num = float.MaxValue;
			bool flag = false;
			for (int i = 0; i < ((Transform)val2).childCount; i++)
			{
				Transform child = ((Transform)val2).GetChild(i);
				if ((Object)(object)child == (Object)null || !((Component)child).gameObject.activeSelf)
				{
					continue;
				}
				RectTransform val3 = (RectTransform)(object)((child is RectTransform) ? child : null);
				if (!((Object)(object)val3 == (Object)null))
				{
					val3.GetWorldCorners(Corners);
					float y = ((Transform)space).InverseTransformPoint(Corners[0]).y;
					if (y < num)
					{
						num = y;
						flag = true;
					}
				}
			}
			if (!flag)
			{
				return false;
			}
			bottomLocalY = num;
			return true;
		}

		private static Button MakeButton(Button template, string name, UnityAction onClick, out TMP_Text label)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			Button val = Object.Instantiate<Button>(template, (Transform)(object)_bar);
			((Object)val).name = name;
			Localize componentInChildren = ((Component)val).GetComponentInChildren<Localize>(true);
			if ((Object)(object)componentInChildren != (Object)null)
			{
				Object.DestroyImmediate((Object)(object)componentInChildren);
			}
			UIGamePad[] componentsInChildren = ((Component)val).GetComponentsInChildren<UIGamePad>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				Object.DestroyImmediate((Object)(object)componentsInChildren[i]);
			}
			val.onClick = new ButtonClickedEvent();
			((UnityEvent)val.onClick).AddListener(onClick);
			RectTransform component = ((Component)template).GetComponent<RectTransform>();
			LayoutElement obj = ((Component)val).gameObject.AddComponent<LayoutElement>();
			Rect rect = component.rect;
			obj.preferredWidth = ((Rect)(ref rect)).width;
			rect = component.rect;
			obj.preferredHeight = ((Rect)(ref rect)).height;
			label = ((Component)val).GetComponentInChildren<TMP_Text>();
			if ((Object)(object)label != (Object)null)
			{
				float fontSize = label.fontSize;
				label.enableAutoSizing = true;
				label.fontSizeMax = fontSize;
				label.fontSizeMin = fontSize - 4f;
			}
			return val;
		}

		private static void OnSorterClick()
		{
			if (!((Object)(object)_current == (Object)null))
			{
				bool flag = !SorterZdo.IsSorter(_current);
				SorterZdo.SetSorter(_current, flag);
				ClearPending();
				Msg(flag ? "Sorter enabled. Contents distribute when the chest is closed" : "Sorter disabled");
				Refresh();
			}
		}

		private static void OnPinClick()
		{
			if ((Object)(object)_current == (Object)null)
			{
				return;
			}
			if (Filters.GetPinned(_current).Count <= 0)
			{
				if (Filters.PinContents(_current) > 0)
				{
					Filters.SetManual(_current, manual: false);
					List<string> pinned = Filters.GetPinned(_current);
					Msg("Pinned: " + string.Join(", ", pinned) + " (auto-fill on)");
					Plugin.Log.LogInfo((object)("[pin] " + string.Join(", ", pinned)));
				}
				else
				{
					bool flag = !Filters.GetManual(_current);
					Filters.SetManual(_current, flag);
					Msg(flag ? "Chest is empty - marked Manual. Organize will not claim or fill it" : "Manual off. Organize may claim this empty chest as a home");
				}
			}
			else
			{
				bool flag2 = !Filters.GetManual(_current);
				Filters.SetManual(_current, flag2);
				Msg(flag2 ? "Auto-fill off. This chest only fills when you click Pull" : "Auto-fill on. The sorter routes matching items here");
			}
			Refresh();
		}

		private static void OnClearClick()
		{
			if (!((Object)(object)_current == (Object)null))
			{
				Filters.ClearPinned(_current);
				Filters.SetManual(_current, manual: false);
				Filters.ClearHome(_current);
				Msg("Filters cleared");
				Refresh();
			}
		}

		private static void OnPullClick()
		{
			if (!((Object)(object)_current == (Object)null))
			{
				Puller.PullInto(_current, out var movedTotal, out var typesMoved);
				Msg((movedTotal > 0) ? ("Pulled " + movedTotal + " item" + ((movedTotal == 1) ? "" : "s") + " (" + typesMoved + " type" + ((typesMoved == 1) ? "" : "s") + ")") : "Nothing to pull from nearby chests");
			}
		}

		private static void OnOrganizeClick()
		{
			if ((Object)(object)_current == (Object)null)
			{
				return;
			}
			if (_pendingPlan != null && (Object)(object)_pendingChest == (Object)(object)_current && Time.time - _pendingAt <= 5f)
			{
				if (!(Time.time - _pendingAt < 0.3f))
				{
					Container current = _current;
					ClearPending();
					OrganizePlan organizePlan = Organizer.BuildPlan(current, Plugin.SorterRadius.Value);
					if (organizePlan.IsEmpty)
					{
						Msg("Nothing left to organize");
					}
					else
					{
						Organizer.Execute(organizePlan);
					}
				}
				return;
			}
			OrganizePlan organizePlan2 = Organizer.BuildPlan(_current, Plugin.SorterRadius.Value);
			if (organizePlan2.IsEmpty)
			{
				ClearPending();
				Msg("Nothing to organize");
				return;
			}
			_pendingPlan = organizePlan2;
			_pendingChest = _current;
			_pendingAt = Time.time;
			OrganizeSummary summary = organizePlan2.Summary;
			if ((Object)(object)_organizeLabel != (Object)null)
			{
				_organizeLabel.text = "Confirm?";
			}
			Plugin.Log.LogInfo((object)("[organize] plan ready: " + summary.TotalItems + " items -> " + summary.TargetChests + " chest(s) from " + summary.SourceChests + " source(s); awaiting confirm"));
			string text = "Organize: move " + summary.TotalItems + " item" + ((summary.TotalItems == 1) ? "" : "s") + " across " + summary.TargetChests + " chest" + ((summary.TargetChests == 1) ? "" : "s");
			if (summary.HomelessItems > 0)
			{
				text = text + " (" + summary.HomelessItems + " won't fit - add more chests)";
			}
			Msg(text + " - press again to confirm");
		}

		private static void ClearPending()
		{
			_pendingPlan = null;
			_pendingChest = null;
			_pendingAt = 0f;
			if ((Object)(object)_organizeLabel != (Object)null)
			{
				_organizeLabel.text = "Organize";
			}
		}

		private static void Refresh()
		{
			if ((Object)(object)_bar == (Object)null)
			{
				return;
			}
			bool flag = (Object)(object)_current != (Object)null && SorterZdo.HasValidNView(_current);
			((Component)_bar).gameObject.SetActive(flag);
			if (!flag)
			{
				return;
			}
			bool flag2 = SorterZdo.IsSorter(_current);
			_sorterLabel.text = (flag2 ? "Sorter: ON" : "Sorter: OFF");
			bool flag3 = !flag2;
			bool flag4 = false;
			bool flag5 = false;
			if (flag3)
			{
				int count = Filters.GetPinned(_current).Count;
				bool manual = Filters.GetManual(_current);
				FilterSpec spec = Filters.GetSpec(_current);
				_pinLabel.text = ((count != 0) ? (manual ? ("Manual (" + count + ")") : ("Auto (" + count + ")")) : (manual ? "Manual" : "Pin"));
				flag4 = count > 0 || manual || !string.IsNullOrEmpty(spec.Home);
				if (flag4)
				{
					_clearLabel.text = "Clear";
				}
				flag5 = spec.HasExplicit;
				if (flag5)
				{
					_pullLabel.text = "Pull";
				}
			}
			if (flag2)
			{
				_organizeLabel.text = ((_pendingPlan != null && (Object)(object)_pendingChest == (Object)(object)_current) ? "Confirm?" : "Organize");
			}
			((Component)_pinBtn).gameObject.SetActive(flag3);
			((Component)_clearBtn).gameObject.SetActive(flag4);
			((Component)_pullBtn).gameObject.SetActive(flag5);
			((Component)_organizeBtn).gameObject.SetActive(flag2);
			LinkGamepadNav();
		}

		private static void LinkGamepadNav()
		{
			if (!((Object)(object)_bar == (Object)null))
			{
				List<Selectable> row = new List<Selectable>
				{
					(Selectable)(object)_sorterBtn,
					(Selectable)(object)_pinBtn,
					(Selectable)(object)_clearBtn,
					(Selectable)(object)_pullBtn,
					(Selectable)(object)_organizeBtn
				};
				GamepadNav.LinkRow(row);
				InventoryGui instance = InventoryGui.instance;
				if ((Object)(object)instance != (Object)null && (Object)(object)instance.m_takeAllButton != (Object)null)
				{
					GamepadNav.AttachRowToAnchor((Selectable)(object)instance.m_takeAllButton, row);
				}
			}
		}

		private static void Msg(string text)
		{
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false);
			}
		}
	}
	[HarmonyPatch(typeof(InventoryHandler), "RPC_RequestItemRemoveResponse", new Type[]
	{
		typeof(Inventory),
		typeof(RequestChestRemoveResponse)
	})]
	internal static class InventoryHandler_RemoveResponse_Patch
	{
		private static void Postfix(RequestChestRemoveResponse response)
		{
			if (response != null)
			{
				MucResults.RecordRemove(response.SourceID, response.Success, response.Amount);
			}
		}
	}
	[HarmonyPatch]
	internal static class PlaceGatherPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(Player), "UpdatePlacement", new Type[2]
			{
				typeof(bool),
				typeof(float)
			}, (Type[])null);
		}

		[HarmonyPrefix]
		private static void Prefix(Player __instance, bool takeInput)
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			if (!Gather.IsEnabled || !Gather.IsBuildEnabled || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || !takeInput || !((Character)__instance).InPlaceMode() || Hud.IsPieceSelectionVisible() || __instance.PlacementCostDisabled || (!ZInput.GetButtonDown("Attack") && !ZInput.GetButtonDown("JoyPlace")) || ZInput.GetButton("AltPlace") || ZInput.GetButton("JoyAltKeys"))
			{
				return;
			}
			Piece selectedPiece = __instance.GetSelectedPiece();
			if ((Object)(object)selectedPiece == (Object)null || selectedPiece.m_resources == null || selectedPiece.m_resources.Length == 0 || __instance.HaveRequirements(selectedPiece, (RequirementMode)0) || (int)__instance.GetPlacementStatus() != 0)
			{
				return;
			}
			BuildGather.Invalidate();
			List<GatherNeed> list = GatherMath.Resolve(BuildGather.NeedsFor(selectedPiece), onlyOneIngredient: false);
			if (list.Count != 0)
			{
				Gatherer.Pull(list, out var movedTotal, out var typesMoved);
				BuildGather.Invalidate();
				if (movedTotal > 0)
				{
					Plugin.Log.LogInfo((object)("[gather] place-triggered pull: " + movedTotal + " item(s), " + typesMoved + " type(s) for " + ((Object)selectedPiece).name));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Smelter), "Awake")]
	internal static class Smelter_Awake_Patch
	{
		private static void Postfix(Smelter __instance)
		{
			Stations.RegisterProcessor((Component)(object)__instance, __instance.m_name);
		}
	}
	[HarmonyPatch(typeof(Smelter), "OnDestroyed")]
	internal static class Smelter_OnDestroyed_Patch
	{
		private static void Prefix(Smelter __instance)
		{
			Stations.UnregisterProcessor((Component)(object)__instance);
		}
	}
	[HarmonyPatch(typeof(Fermenter), "Awake")]
	internal static class Fermenter_Awake_Patch
	{
		private static void Postfix(Fermenter __instance)
		{
			Stations.RegisterProcessor((Component)(object)__instance, __instance.m_name);
		}
	}
	[HarmonyPatch(typeof(Fermenter), "OnDestroyed")]
	internal static class Fermenter_OnDestroyed_Patch
	{
		private static void Prefix(Fermenter __instance)
		{
			Stations.UnregisterProcessor((Component)(object)__instance);
		}
	}
	[HarmonyPatch(typeof(CookingStation), "Awake")]
	internal static class CookingStation_Awake_Patch
	{
		private static void Postfix(CookingStation __instance)
		{
			Stations.RegisterProcessor((Component)(object)__instance, __instance.m_name);
		}
	}
	[HarmonyPatch(typeof(CookingStation), "OnDestroyed")]
	internal static class CookingStation_OnDestroyed_Patch
	{
		private static void Prefix(CookingStation __instance)
		{
			Stations.UnregisterProcessor((Component)(object)__instance);
		}
	}
	[HarmonyPatch(typeof(Sign), "Awake")]
	internal static class Sign_Awake_Patch
	{
		private static void Postfix(Sign __instance)
		{
			Filters.RegisterSign(__instance);
		}
	}
	[HarmonyPatch(typeof(Sign), "SetText")]
	internal static class Sign_SetText_Patch
	{
		private static void Postfix()
		{
			Filters.InvalidateAll();
		}
	}
}
namespace ChestButler.Core
{
	internal static class BucketKeys
	{
		internal const string GearPrefix = "gear:";

		internal const string Weapons = "gear:weapons";

		internal const string Armor = "gear:armor";

		internal const string Tools = "gear:tools";

		internal const string GearMisc = "gear:misc";

		internal const string TypePrefix = "item:";

		internal const string Misc = "misc";

		internal static string ForType(string norm)
		{
			return "item:" + norm;
		}

		internal static bool IsGear(string key)
		{
			return key?.StartsWith("gear:", StringComparison.Ordinal) ?? false;
		}

		internal static bool IsPerType(string key)
		{
			return key?.StartsWith("item:", StringComparison.Ordinal) ?? false;
		}

		internal static string TypeOf(string key)
		{
			if (!IsPerType(key))
			{
				return null;
			}
			return key.Substring("item:".Length);
		}

		internal static string Label(string key)
		{
			if (string.IsNullOrEmpty(key))
			{
				return "?";
			}
			if (IsGear(key))
			{
				return key.Substring("gear:".Length);
			}
			if (IsPerType(key))
			{
				return key.Substring("item:".Length);
			}
			return key;
		}
	}
	internal static class BuildGather
	{
		private const float SourceMaxAge = 1f;

		private static List<Container> _sources;

		private static float _sourcesAt = float.NegativeInfinity;

		internal static List<Container> SourcesCached()
		{
			float unscaledTime = Time.unscaledTime;
			if (_sources == null || unscaledTime - _sourcesAt > 1f)
			{
				_sources = Gatherer.Sources();
				_sourcesAt = unscaledTime;
			}
			return _sources;
		}

		internal static void Invalidate()
		{
			_sources = null;
		}

		internal static List<GatherNeed> NeedsFor(Piece piece)
		{
			List<GatherNeed> list = new List<GatherNeed>();
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)piece == (Object)null || (Object)(object)localPlayer == (Object)null || piece.m_resources == null)
			{
				return list;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			List<Container> sources = SourcesCached();
			Requirement[] resources = piece.m_resources;
			foreach (Requirement val in resources)
			{
				SharedData val2 = val?.m_resItem?.m_itemData?.m_shared;
				if (val2 != null && !string.IsNullOrEmpty(val2.m_name) && val.m_amount > 0)
				{
					list.Add(new GatherNeed
					{
						SharedName = val2.m_name,
						Display = Names.Normalize(val2.m_name),
						Needed = val.m_amount,
						InPlayer = ((inventory != null) ? inventory.CountItems(val2.m_name, -1, true) : 0),
						InStorage = Gatherer.CountInStorage(sources, val2.m_name)
					});
				}
			}
			return list;
		}
	}
	internal static class ContainerTracker
	{
		internal struct Candidate
		{
			internal Container Chest;

			internal float Distance;
		}

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

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

		internal static void Register(Container c)
		{
			if (!((Object)(object)c == (Object)null) && !((Object)(object)((Component)c).GetComponentInParent<Incinerator>() != (Object)null))
			{
				All.Add(c);
				if ((Object)(object)((Component)c).GetComponentInParent<Vagon>() != (Object)null || (Object)(object)((Component)c).GetComponentInParent<Ship>() != (Object)null)
				{
					Vehicles.Add(c);
				}
			}
		}

		internal static void Unregister(Container c)
		{
			if (!((Object)(object)c == (Object)null))
			{
				All.Remove(c);
				Vehicles.Remove(c);
				Filters.Invalidate(c);
			}
		}

		internal static List<Container> Candidates(Container sorter, float radius, bool excludeSorters = true)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return Accessible(((Component)sorter).transform.position, radius, sorter, excludeSorters);
		}

		internal static List<Container> AccessibleNear(Vector3 point, float radius, bool excludeSorters = false)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Accessible(point, radius, null, excludeSorters);
		}

		private static List<Container> Accessible(Vector3 pos, float radius, Container exclude, bool excludeSorters)
		{
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			All.RemoveWhere((Container c) => (Object)(object)c == (Object)null);
			List<float> dists = new List<float>();
			List<Container> found = new List<Container>();
			bool flag = Plugin.VehiclesAreStorage != null && Plugin.VehiclesAreStorage.Value;
			foreach (Container item in All)
			{
				if (!((Object)(object)item == (Object)(object)exclude) && (flag || !Vehicles.Contains(item)) && SorterZdo.HasValidNView(item) && item.GetInventory() != null && !((Object)(object)((Component)item).GetComponentInParent<Piece>() == (Object)null))
				{
					float num = Vector3.Distance(pos, ((Component)item).transform.position);
					if (!(num > radius) && (!excludeSorters || !SorterZdo.IsSorter(item)) && SorterZdo.PlayerCanAccess(item) && PrivateArea.CheckAccess(((Component)item).transform.position, 0f, false, true))
					{
						dists.Add(num);
						found.Add(item);
					}
				}
			}
			List<int> list = new List<int>(found.Count);
			for (int num2 = 0; num2 < found.Count; num2++)
			{
				list.Add(num2);
			}
			list.Sort(delegate(int a, int b)
			{
				int num3 = dists[a].CompareTo(dists[b]);
				return (num3 != 0) ? num3 : CompareUid(found[a], found[b]);
			});
			List<Container> list2 = new List<Container>(list.Count);
			foreach (int item2 in list)
			{
				list2.Add(found[item2]);
			}
			return list2;
		}

		internal static List<Candidate> CandidatesWithDistance(Container sorter, float radius, bool excludeSorters = true)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			List<Container> list = Candidates(sorter, radius, excludeSorters);
			List<Candidate> list2 = new List<Candidate>(list.Count);
			Vector3 position = ((Component)sorter).transform.position;
			foreach (Container item in list)
			{
				list2.Add(new Candidate
				{
					Chest = item,
					Distance = Vector3.Distance(position, ((Component)item).transform.position)
				});
			}
			return list2;
		}

		internal static int CompareUid(Container a, Container b)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = SorterZdo.NView(a);
			ZNetView val2 = SorterZdo.NView(b);
			if ((Object)(object)val == (Object)null || !val.IsValid())
			{
				if (!((Object)(object)val2 == (Object)null) && val2.IsValid())
				{
					return 1;
				}
				return 0;
			}
			if ((Object)(object)val2 == (Object)null || !val2.IsValid())
			{
				return -1;
			}
			return ((ZDOID)(ref val.GetZDO().m_uid)).CompareTo(val2.GetZDO().m_uid);
		}

		internal static Container NearestTo(Vector3 point, float maxRange)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			Container result = null;
			float num = maxRange;
			foreach (Container item in All)
			{
				if (!((Object)(object)item == (Object)null))
				{
					float num2 = Vector3.Distance(point, ((Component)item).transform.position);
					if (num2 < num)
					{
						num = num2;
						result = item;
					}
				}
			}
			return result;
		}
	}
	internal sealed class FilterSpec
	{
		internal readonly HashSet<string> Items = new HashSet<string>();

		internal readonly HashSet<string> GroupNames = new HashSet<string>();

		internal int Priority;

		internal bool Ignore;

		internal bool ManualOnly;

		internal string Home;

		internal bool HasExplicit
		{
			get
			{
				if (Items.Count <= 0)
				{
					return GroupNames.Count > 0;
				}
				return true;
			}
		}

		internal bool MatchesItem(string normName)
		{
			foreach (string item in Items)
			{
				if (Names.Matches(item, normName))
				{
					return true;
				}
			}
			return false;
		}

		internal bool MatchesGroup(string normName)
		{
			foreach (string groupName in GroupNames)
			{
				if (Groups.GroupContains(groupName, normName))
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class Filters
	{
		private const float SignRange = 2.5f;

		private const float CacheTtl = 30f;

		private static readonly int ItemsHash = StringExtensionMethods.GetStableHashCode("psort_items");

		private static readonly int ManualHash = StringExtensionMethods.GetStableHashCode("psort_manual");

		private static readonly int HomeHash = StringExtensionMethods.GetStableHashCode("psort_home");

		private static readonly HashSet<Sign> Signs = new HashSet<Sign>();

		private static readonly Dictionary<Container, KeyValuePair<float, FilterSpec>> Cache = new Dictionary<Container, KeyValuePair<float, FilterSpec>>();

		internal static void RegisterSign(Sign s)
		{
			if ((Object)(object)s != (Object)null)
			{
				Signs.Add(s);
				InvalidateAll();
			}
		}

		internal static void Invalidate(Container c)
		{
			if ((Object)(object)c != (Object)null)
			{
				Cache.Remove(c);
			}
		}

		internal static void InvalidateAll()
		{
			Cache.Clear();
		}

		internal static List<string> GetPinned(Container c)
		{
			List<string> list = new List<string>();
			ZNetView val = SorterZdo.NView(c);
			if ((Object)(object)val == (Object)null || !val.IsValid())
			{
				return list;
			}
			string[] array = val.GetZDO().GetString(ItemsHash, "").Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					list.Add(text);
				}
			}
			return list;
		}

		internal static void SetPinned(Container c, IEnumerable<string> tokens)
		{
			ZNetView val = SorterZdo.NView(c);
			if (!((Object)(object)val == (Object)null) && val.IsValid())
			{
				if (!val.IsOwner())
				{
					val.ClaimOwnership();
				}
				val.GetZDO().Set(ItemsHash, string.Join(",", tokens));
				Cache.Remove(c);
			}
		}

		internal static int PinContents(Container c)
		{
			Inventory inventory = c.GetInventory();
			if (inventory == null)
			{
				return 0;
			}
			HashSet<string> hashSet = new HashSet<string>();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem?.m_shared != null)
				{
					string text = Names.Normalize(allItem.m_shared.m_name);
					if (text.Length > 0)
					{
						hashSet.Add(text);
					}
				}
			}
			if (hashSet.Count > 0)
			{
				SetPinned(c, hashSet);
			}
			return hashSet.Count;
		}

		internal static void ClearPinned(Container c)
		{
			SetPinned(c, new string[0]);
		}

		internal static bool GetManual(Container c)
		{
			ZNetView val = SorterZdo.NView(c);
			if ((Object)(object)val != (Object)null && val.IsValid())
			{
				return val.GetZDO().GetBool(ManualHash, false);
			}
			return false;
		}

		internal static void SetManual(Container c, bool manual)
		{
			ZNetView val = SorterZdo.NView(c);
			if (!((Object)(object)val == (Object)null) && val.IsValid())
			{
				if (!val.IsOwner())
				{
					val.ClaimOwnership();
				}
				val.GetZDO().Set(ManualHash, manual);
				Cache.Remove(c);
			}
		}

		internal static string GetHome(Container c)
		{
			ZNetView val = SorterZdo.NView(c);
			if ((Object)(object)val == (Object)null || !val.IsValid())
			{
				return null;
			}
			string text = val.GetZDO().GetString(HomeHash, "");
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return null;
		}

		internal static void SetHome(Container c, string bucketKey)
		{
			ZNetView val = SorterZdo.NView(c);
			if (!((Object)(object)val == (Object)null) && val.IsValid())
			{
				if (!val.IsOwner())
				{
					val.ClaimOwnership();
				}
				val.GetZDO().Set(HomeHash, bucketKey ?? "");
				Cache.Remove(c);
			}
		}

		internal static void ClearHome(Container c)
		{
			SetHome(c, null);
		}

		internal static FilterSpec GetSpec(Container c)
		{
			if (Cache.TryGetValue(c, out var value) && Time.time - value.Key < 30f)
			{
				return value.Value;
			}
			FilterSpec filterSpec = new FilterSpec();
			foreach (string item in GetPinned(c))
			{
				filterSpec.Items.Add(item);
			}
			filterSpec.ManualOnly = GetManual(c);
			filterSpec.Home = GetHome(c);
			ParseNearestSign(c, filterSpec);
			Cache[c] = new KeyValuePair<float, FilterSpec>(Time.time, filterSpec);
			return filterSpec;
		}

		private static void ParseNearestSign(Container c, FilterSpec spec)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: 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)
			Signs.RemoveWhere((Sign s) => (Object)(object)s == (Object)null);
			Vector3 position = ((Component)c).transform.position;
			foreach (Sign sign in Signs)
			{
				string text = sign.GetText();
				if (string.IsNullOrEmpty(text))
				{
					continue;
				}
				SignSpec signSpec = SignGrammar.Parse(text);
				float num = Vector3.Distance(((Component)sign).transform.position, position);
				if (signSpec.HasOff && signSpec.AreaOffRadius > 0f && num <= signSpec.AreaOffRadius)
				{
					spec.Ignore = true;
				}
				if (num > 2.5f || !IsNearestContainerTo(((Component)sign).transform.position, c))
				{
					continue;
				}
				if (signSpec.HasOff)
				{
					spec.Ignore = true;
				}
				foreach (string token in signSpec.Tokens)
				{
					if (token.Length >= 2 && token[0] == 'p' && int.TryParse(token.Substring(1), out var result))
					{
						spec.Priority = result;
					}
					else if (Groups.IsGroup(token))
					{
						spec.GroupNames.Add(token);
					}
					else
					{
						spec.Items.Add(token);
					}
				}
			}
		}

		private static bool IsNearestContainerTo(Vector3 signPos, Container candidate)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			Container val = ContainerTracker.NearestTo(signPos, 2.5f);
			if (!((Object)(object)val == (Object)null))
			{
				return (Object)(object)val == (Object)(object)candidate;
			}
			return true;
		}
	}
	internal static class GamepadNav
	{
		private static ConfigEntry<bool> _enabled;

		private static bool _bound;

		internal static bool Enabled
		{
			get
			{
				//IL_003b: 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_0048: Expected O, but got Unknown
				//IL_0048: Unknown result type (might be due to invalid IL or missing references)
				//IL_0052: Expected O, but got Unknown
				if (!_bound)
				{
					_bound = true;
					Plugin instance = Plugin.Instance;
					if ((Object)(object)instance != (Object)null)
					{
						_enabled = ((BaseUnityPlugin)instance).Config.Bind<bool>("Gamepad", "Enabled", true, new ConfigDescription("Let a controller reach ChestButler's buttons by linking them into the panel's D-pad navigation. Client-side.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
						{
							IsAdminOnly = false
						} }));
					}
				}
				if (_enabled != null)
				{
					return _enabled.Value;
				}
				return true;
			}
		}

		internal static void LinkRow(IList<Selectable> row)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			if (!Enabled || row == null)
			{
				return;
			}
			List<Selectable> list = new List<Selectable>();
			for (int i = 0; i < row.Count; i++)
			{
				Selectable val = row[i];
				if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy)
				{
					list.Add(val);
				}
			}
			if (list.Count != 0)
			{
				for (int j = 0; j < list.Count; j++)
				{
					Navigation navigation = list[j].navigation;
					((Navigation)(ref navigation)).mode = (Mode)4;
					((Navigation)(ref navigation)).selectOnLeft = ((j > 0) ? list[j - 1] : null);
					((Navigation)(ref navigation)).selectOnRight = ((j < list.Count - 1) ? list[j + 1] : null);
					list[j].navigation = navigation;
				}
			}
		}

		internal static void LinkVertical(Selectable above, Selectable below, bool onlyIfVanillaEmpty)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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_004d: 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)
			if (Enabled && !((Object)(object)above == (Object)null) && !((Object)(object)below == (Object)null))
			{
				Navigation navigation = above.navigation;
				Navigation navigation2 = below.navigation;
				bool flag = false;
				if (!onlyIfVanillaEmpty || (Object)(object)((Navigation)(ref navigation)).selectOnDown == (Object)null)
				{
					((Navigation)(ref navigation)).mode = (Mode)4;
					((Navigation)(ref navigation)).selectOnDown = below;
					above.navigation = navigation;
					flag = true;
				}
				((Navigation)(ref navigation2)).mode = (Mode)4;
				((Navigation)(ref navigation2)).selectOnUp = above;
				below.navigation = navigation2;
				if (!flag)
				{
					Plugin.Log.LogDebug((object)("[gamepad] '" + ((Object)above).name + "' already navigates down to '" + (((Object)(object)((Navigation)(ref navigation)).selectOnDown != (Object)null) ? ((Object)((Navigation)(ref navigation)).selectOnDown).name : "?") + "'; left it alone. Our button is reachable upward from '" + ((Object)below).name + "' only."));
				}
			}
		}

		internal static void AttachRowToAnchor(Selectable anchor, IList<Selectable> row)
		{
			if (!Enabled || (Object)(object)anchor == (Object)null || row == null)
			{
				return;
			}
			for (int i = 0; i < row.Count; i++)
			{
				Selectable val = row[i];
				if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy)
				{
					LinkVertical(anchor, val, onlyIfVanillaEmpty: true);
					break;
				}
			}
		}
	}
	internal static class Gather
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> BuildEnabled;

		internal static ConfigEntry<bool> ShowStorageCounts;

		internal static bool IsEnabled
		{
			get
			{
				if (Enabled != null)
				{
					return Enabled.Value;
				}
				return true;
			}
		}

		internal static bool IsBuildEnabled
		{
			get
			{
				if (BuildEnabled != null)
				{
					return BuildEnabled.Value;
				}
				return true;
			}
		}

		internal static bool CountsShown
		{
			get
			{
				if (ShowStorageCounts != null)
				{
					return ShowStorageCounts.Value;
				}
				return true;
			}
		}

		internal static void Init(ConfigFile config)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			Enabled = config.Bind<bool>("Gather", "Enabled", true, new ConfigDescription("Show the Gather button in the crafting panel. Client-side.", (AcceptableValueBase)null, Array.Empty<object>()));
			BuildEnabled = config.Bind<bool>("Gather", "BuildEnabled", true, new ConfigDescription("When you place a piece you can't afford, fetch the missing materials from nearby chests. Client-side.", (AcceptableValueBase)null, Array.Empty<object>()));
			ShowStorageCounts = config.Bind<bool>("Gather", "ShowStorageCounts", true, new ConfigDescription("Show \"(N in storage)\" beside each ingredient in the crafting panel. Client-side.", (AcceptableValueBase)null, Array.Empty<object>()));
		}
	}
	internal static class Gatherer
	{
		internal static List<Container> Sources()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			List<Container> list = new List<Container>();
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return list;
			}
			foreach (Container item in ContainerTracker.AccessibleNear(((Component)localPlayer).transform.position, Plugin.SorterRadius.Value))
			{
				if (!Filters.GetSpec(item).Ignore)
				{
					list.Add(item);
				}
			}
			return list;
		}

		internal static int CountInStorage(List<Container> sources, string sharedName)
		{
			if (sources == null || string.IsNullOrEmpty(sharedName))
			{
				return 0;
			}
			int num = 0;
			for (int i = 0; i < sources.Count; i++)
			{
				Inventory inventory = sources[i].GetInventory();
				if (inventory != null)
				{
					num += inventory.CountItems(sharedName, -1, true);
				}
			}
			return num;
		}

		internal static void Pull(List<GatherNeed> needs, out int movedTotal, out int typesMoved)
		{
			//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_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_021c: 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)
			movedTotal = 0;
			typesMoved = 0;
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || needs == null || needs.Count == 0)
			{
				return;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			if (inventory == null)
			{
				return;
			}
			ZDOID zDOID = ((Character)localPlayer).GetZDOID();
			List<Container> list = Sources();
			int num = 0;
			foreach (GatherNeed need in needs)
			{
				int num2 = need.Gatherable;
				if (num2 <= 0)
				{
					continue;
				}
				bool flag = false;
				List<KeyValuePair<int, Container>> list2 = new List<KeyValuePair<int, Container>>();
				for (int i = 0; i < list.Count; i++)
				{
					Inventory inventory2 = list[i].GetInventory();
					if (inventory2 != null)
					{
						int num3 = inventory2.CountItems(need.SharedName, -1, true);
						if (num3 > 0)
						{
							list2.Add(new KeyValuePair<int, Container>(num3, list[i]));
						}
					}
				}
				list2.Sort(delegate(KeyValuePair<int, Container> a, KeyValuePair<int, Container> b)
				{
					int num6 = b.Key.CompareTo(a.Key);
					return (num6 == 0) ? ContainerTracker.CompareUid(a.Value, b.Value) : num6;
				});
				foreach (KeyValuePair<int, Container> item in list2)
				{
					if (num2 <= 0)
					{
						break;
					}
					Container value = item.Value;
					Inventory inventory3 = value.GetInventory();
					if (inventory3 == null)
					{
						continue;
					}
					ZNetView val = SorterZdo.NView(value);
					if ((Object)(object)val == (Object)null || !val.IsValid() || !SorterZdo.PlayerCanAccess(value) || !PrivateArea.CheckAccess(((Component)value).transform.position, 0f, false, true))
					{
						continue;
					}
					InventoryBlock val2 = InventoryBlock.Get(inventory3);
					foreach (ItemData item2 in new List<ItemData>(inventory3.GetAllItems()))
					{
						if (num2 <= 0)
						{
							break;
						}
						if (item2?.m_shared != null && !(item2.m_shared.m_name != need.SharedName) && (val2 == null || !val2.IsSlotBlocked(item2.m_gridPos)))
						{
							int num4 = Router.Room(inventory, item2) - num;
							if (num4 <= 0)
							{
								num2 = 0;
								break;
							}
							int num5 = Math.Min(Math.Min(num2, item2.m_stack), num4);
							if (num5 > 0)
							{
								ContainerHandler.RemoveItemFromChest(value, item2, inventory, new Vector2i(-1, -1), zDOID, num5, (ItemData)null);
								num += num5;
								movedTotal += num5;
								num2 -= num5;
								flag = true;
							}
						}
					}
				}
				if (flag)
				{
					typesMoved++;
				}
			}
			if (movedTotal > 0)
			{
				Plugin.Log.LogInfo((object)("[gather] pulled " + movedTotal + " item(s) across " + typesMoved + " type(s)"));
			}
		}
	}
	internal struct GatherNeed
	{
		public string SharedName;

		public string Display;

		public int Needed;

		public int InPlayer;

		public int InStorage;

		public int Shortfall => Math.Max(0, Needed - InPlayer);

		public int Gatherable => Math.Min(Shortfall, InStorage);
	}
	internal static class GatherMath
	{
		internal static List<GatherNeed> Resolve(IList<GatherNeed> raw, bool onlyOneIngredient)
		{
			List<GatherNeed> list = new List<GatherNeed>();
			if (raw == null || raw.Count == 0)
			{
				return list;
			}
			if (!onlyOneIngredient)
			{
				for (int i = 0; i < raw.Count; i++)
				{
					if (raw[i].Gatherable > 0)
					{
						list.Add(raw[i]);
					}
				}
				return list;
			}
			int num = PickSingleIngredient(raw);
			if (num >= 0 && raw[num].Gatherable > 0)
			{
				list.Add(raw[num]);
			}
			return list;
		}

		internal static int PickSingleIngredient(IList<GatherNeed> raw)
		{
			for (int i = 0; i < raw.Count; i++)
			{
				if (raw[i].Shortfall == 0)
				{
					return -1;
				}
			}
			int num = -1;
			for (int j = 0; j < raw.Count; j++)
			{
				if (raw[j].Gatherable <= 0)
				{
					continue;
				}
				if (num < 0)
				{
					num = j;
					continue;
				}
				int num2 = raw[j].Shortfall - raw[j].Gatherable;
				int num3 = raw[num].Shortfall - raw[num].Gatherable;
				if (num2 < num3)
				{
					num = j;
				}
				else if (num2 <= num3)
				{
					if (raw[j].Shortfall < raw[num].Shortfall)
					{
						num = j;
					}
					else if (raw[j].Shortfall <= raw[num].Shortfall && string.CompareOrdinal(raw[j].SharedName ?? "", raw[num].SharedName ?? "") < 0)
					{
						num = j;
					}
				}
			}
			return num;
		}
	}
	internal static class Gear
	{
		private static readonly Dictionary<ItemType, string> ByType = new Dictionary<ItemType, string>
		{
			{
				(ItemType)3,
				"gear:weapons"
			},
			{
				(ItemType)14,
				"gear:weapons"
			},
			{
				(ItemType)22,
				"gear:weapons"
			},
			{
				(ItemType)4,
				"gear:weapons"
			},
			{
				(ItemType)5,
				"gear:weapons"
			},
			{
				(ItemType)15,
				"gear:weapons"
			},
			{
				(ItemType)20,
				"gear:weapons"
			},
			{
				(ItemType)6,
				"gear:armor"
			},
			{
				(ItemType)7,
				"gear:armor"
			},
			{
				(ItemType)11,
				"gear:armor"
			},
			{
				(ItemType)17,
				"gear:armor"
			},
			{
				(ItemType)12,
				"gear:armor"
			},
			{
				(ItemType)18,
				"gear:armor"
			},
			{
				(ItemType)24,
				"gear:armor"
			},
			{
				(ItemType)19,
				"gear:tools"
			}
		};

		private static ConfigEntry<string> _weaponTokens;

		private static ConfigEntry<string> _armorTokens;

		private static ConfigEntry<string> _toolTokens;

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

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

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

		private static readonly HashSet<string> Reported = new HashSet<string>();

		internal static void Init(ConfigFile config)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			_toolTokens = config.Bind<string>("Organize", "ToolTokens", "pickaxe*", new ConfigDescription("Item name tokens ('*' wildcards) forced into the TOOLS gear bucket, overriding the item's own type. Default: pickaxes, which the game classifies as weapons. Comma-separated.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			_weaponTokens = config.Bind<string>("Organize", "WeaponTokens", "", new ConfigDescription("Item name tokens forced into the WEAPONS gear bucket. Comma-separated.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			_armorTokens = config.Bind<string>("Organize", "ArmorTokens", "", new ConfigDescription("Item name tokens forced into the ARMOR gear bucket. Comma-separated.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			config.SettingChanged += delegate
			{
				Rebuild();
			};
			Rebuild();
		}

		private static void Rebuild()
		{
			Fill(Weapon, _weaponTokens);
			Fill(Armour, _armorTokens);
			Fill(Tool, _toolTokens);
			Reported.Clear();
		}

		private static void Fill(List<string> into, ConfigEntry<string> entry)
		{
			into.Clear();
			if (entry == null || string.IsNullOrEmpty(entry.Value))
			{
				return;
			}
			string[] array = entry.Value.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim().ToLowerInvariant();
				if (text.Length > 0)
				{
					into.Add(text);
				}
			}
		}

		internal static string BucketFor(ItemData item, string norm)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			if (item?.m_shared == null)
			{
				return "gear:misc";
			}
			if (MatchesAny(Tool, norm))
			{
				return "gear:tools";
			}
			if (MatchesAny(Weapon, norm))
			{
				return "gear:weapons";
			}
			if (MatchesAny(Armour, norm))
			{
				return "gear:armor";
			}
			if (ByType.TryGetValue(item.m_shared.m_itemType, out var value))
			{
				return value;
			}
			if (Reported.Add(norm))
			{
				Plugin.Log.LogInfo((object)("[organize] '" + norm + "' (item type " + ((object)Unsafe.As<ItemType, ItemType>(ref item.m_shared.m_itemType)/*cast due to .constrained prefix*/).ToString() + ") has no gear bucket - filed under " + BucketKeys.Label("gear:misc") + ". Add it to [Organize] WeaponTokens/ArmorTokens/ToolTokens to place it."));
			}
			return "gear:misc";
		}

		private static bool MatchesAny(List<string> tokens, string norm)
		{
			for (int i = 0; i < tokens.Count; i++)
			{
				if (Names.Matches(tokens[i], norm))
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class Groups
	{
		private static readonly Dictionary<string, ConfigEntry<string>> Entries = new Dictionary<string, ConfigEntry<string>>();

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

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

		private static Dictionary<string, string> Defaults => GroupTables.Defaults;

		private static string[] GroupOrder => GroupTables.GroupOrder;

		internal static void Init(ConfigFile config)
		{
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Expected O, but got Unknown
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Expected O, but got Unknown
			string[] groupOrder = GroupOrder;
			foreach (string text in groupOrder)
			{
				if (!Defaults.ContainsKey(text))
				{
					Plugin.Log.LogError((object)("[groups] GroupOrder lists '" + text + "', which is not a defined group"));
				}
			}
			foreach (KeyValuePair<string, string> @default in Defaults)
			{
				if (Array.IndexOf(GroupOrder, @default.Key) < 0)
				{
					Plugin.Log.LogError((object)("[groups] group '" + @default.Key + "' is missing from GroupOrder; overlap resolution would be non-deterministic for its items"));
				}
			}
			foreach (KeyValuePair<string, string> default2 in Defaults)
			{
				Entries[default2.Key] = config.Bind<string>("ItemGroups", default2.Key, default2.Value, new ConfigDescription("Comma-separated item name tokens ('*' wildcards allowed).", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
				{
					IsAdminOnly = true
				} }));
			}
			config.SettingChanged += delegate
			{
				Rebuild();
			};
			Rebuild();
		}

		private static void Rebuild()
		{
			Parsed.Clear();
			Ordered.Clear();
			string[] array;
			foreach (KeyValuePair<string, ConfigEntry<string>> entry in Entries)
			{
				List<string> list = new List<string>();
				array = entry.Value.Value.Split(',');
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim().ToLowerInvariant();
					if (text.Length > 0)
					{
						list.Add(text);
					}
				}
				Parsed[entry.Key] = list;
			}
			array = GroupOrder;
			foreach (string text2 in array)
			{
				if (Parsed.ContainsKey(text2))
				{
					Ordered.Add(text2);
				}
			}
			List<string> list2 = new List<string>();
			foreach (KeyValuePair<string, List<string>> item in Parsed)
			{
				if (Array.IndexOf(GroupOrder, item.Key) < 0)
				{
					list2.Add(item.Key);
				}
			}
			list2.Sort(StringComparer.Ordinal);
			Ordered.AddRange(list2);
		}

		internal static bool IsGroup(string name)
		{
			return Parsed.ContainsKey(name);
		}

		internal static IReadOnlyList<string> GroupsInOrder()
		{
			return Ordered;
		}

		internal static string FirstGroupFor(string normName)
		{
			for (int i = 0; i < Ordered.Count; i++)
			{
				if (GroupContains(Ordered[i], normName))
				{
					return Ordered[i];
				}
			}
			return null;
		}

		internal static bool GroupContains(string group, string normName)
		{
			if (!Parsed.TryGetValue(group, out var value))
			{
				return false;
			}
			foreach (string item in value)
			{
				if (Names.Matches(item, normName))
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class GroupTables
	{
		internal static readonly Dictionary<string, string> Defaults = new Dictionary<string, string>
		{
			{ "stone", "stone, flint, obsidian, blackmarble, grausten*" },
			{ "wood", "wood, finewood, roundlog, elderbark, yggdrasilwood, blackwood" },
			{ "ores", "*ore, ironscrap, bronzescrap, copperscrap, flametalore*" },
			{ "metals", "copper, tin, bronze, iron, silver, blackmetal*, flametal*" },
			{ "cooking", "carrot, turnip, onion, raspberries, blueberries, cloudberries, mushroom*, jotunpuffs, magecap, honey, barley, bread*, sausages" },
			{ "meat", "*meat*, necktail, entrails, bloodbag, fish*" },
			{ "seeds", "*seeds*, acorn, ancientseed, beechnut, carrotseed, turnipseed, onionseed" },
			{ "trophies", "trophy*" },
			{ "valuables", "coins, ruby, amber, amberpearl, silvernecklace" },
			{ "meads", "mead*, barleywine*" },
			{ "ammo", "arrow*, bolt*, turretbolt*" },
			{ "hides", "*hide*, *pelt*, leatherscraps, chitin" },
			{ "fuel", "coal" }
		};

		internal static readonly string[] GroupOrder = new string[13]
		{
			"metals", "ores", "stone", "wood", "fuel", "cooking", "meat", "seeds", "meads", "ammo",
			"hides", "valuables", "trophies"
		};
	}
	internal static class MucResults
	{
		private struct Result
		{
			public bool Success;

			public int Amount;
		}

		private static readonly Dictionary<int, Result> Removes = new Dictionary<int, Result>();

		internal static void RecordRemove(int requestId, bool success, int amount)
		{
			if (Removes.Count > 256)
			{
				Removes.Clear();
			}
			Removes[requestId] = new Result
			{
				Success = success,
				Amount = amount
			};
		}

		internal static bool TryTakeRemove(int requestId, out bool success, out int amount)
		{
			if (Removes.TryGetValue(requestId, out var value))
			{
				Removes.Remove(requestId);
				success = value.Success;
				amount = value.Amount;
				return true;
			}
			success = false;
			amount = 0;
			return false;
		}

		internal static void Clear()
		{
			Removes.Clear();
		}
	}
	internal static class Names
	{
		private struct Token
		{
			public string Core;

			public bool Leading;

			public bool Trailing;
		}

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

		private static readonly Dictionary<string, Token> TokenCache = new Dictionary<string, Token>(256);

		internal static string Normalize(string sharedName)
		{
			if (string.IsNullOrEmpty(sharedName))
			{
				return string.Empty;
			}
			if (NormCache.TryGetValue(sharedName, out var value))
			{
				return value;
			}
			string text = sharedName;
			if (text.StartsWith("$item_", StringComparison.Ordinal))
			{
				text = text.Substring(6);
			}
			else if (text[0] == '$')
			{
				text = text.Substring(1);
			}
			string text2 = text.Replace("_", "").Trim().ToLowerInvariant();
			NormCache[sharedName] = text2;
			return text2;
		}

		internal static bool Matches(string token, string normName)
		{
			if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(normName))
			{
				return false;
			}
			if (!TokenCache.TryGetValue(token, out var value))
			{
				value = new Token
				{
					Leading = (token[0] == '*'),
					Trailing = (token[token.Length - 1] == '*'),
					Core = token.Trim('*')
				};
				TokenCache[token] = value;
			}
			if (value.Core.Length == 0)
			{
				return false;
			}
			if (value.Leading && value.Trailing)
			{
				return normName.IndexOf(value.Core, StringComparison.Ordinal) >= 0;
			}
			if (value.Trailing)
			{
				return normName.StartsWith(value.Core, StringComparison.Ordinal);
			}
			if (value.Leading)
			{
				return normName.EndsWith(value.Core, StringComparison.Ordinal);
			}
			return string.Equals(normName, value.Core, StringComparison.Ordinal);
		}
	}
	internal static class OrganizeConfig
	{
		internal static ConfigEntry<float> StationRange;

		internal static ConfigEntry<int> MovesPerSecond;

		internal static ConfigEntry<int> MaxMovesPerRun;

		internal static ConfigEntry<bool> IncludeGear;

		internal static ConfigEntry<int> MiscPromoteSlots;

		internal static int MovesPerSecondValue
		{
			get
			{
				if (MovesPerSecond == null)
				{
					return 25;
				}
				return MovesPerSecond.Value;
			}
		}

		internal static int MaxMovesPerRunValue
		{
			get
			{
				if (MaxMovesPerRun == null)
				{
					return 500;
				}
				return MaxMovesPerRun.Value;
			}
		}

		internal static void Init(ConfigFile config)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Expected O, but got Unknown
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected O, but got Unknown
			//IL_00c5: 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_00d2: Expected O, but got Unknown
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Expected O, but got Unknown
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Expected O, but got Unknown
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Expected O, but got Unknown
			MovesPerSecond = config.Bind<int>("Organize", "MovesPerSecond", 25, new ConfigDescription("How many item transfers per second the Organize sweep issues (a real per-second rate, independent of your framerate). The mod measures its own cost and backs off below this on its own. Client-side: lower it if Organize costs you frames.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 100), Array.Empty<object>()));
			MaxMovesPerRun = config.Bind<int>("Organize", "MaxMovesPerRun", 500, new ConfigDescription("Safety cap on transfers per Organize press. On a very large base the run stops at this many and tells you to press Organize again to continue. Client-side.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(50, 5000), Array.Empty<object>()));
			StationRange = config.Bind<float>("Organize", "StationRange", 8f, new ConfigDescription("Max distance (m) from a chest to a crafting station for the chest to inherit that station's item groups during Organize. Nearest mapped station wins. NOTE: this sets the match distance only - station detection scans every loaded station regardless.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			IncludeGear = config.Bind<bool>("Organize", "IncludeGear", true, new ConfigDescription("Sweep weapons, armor and tools into their own chests during Organize. Off leaves gear where it is unless a chest explicitly pins it (the 1.1.x behaviour).", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			MiscPromoteSlots = config.Bind<int>("Organize", "MiscPromoteSlots", 24, new ConfigDescription("An ungrouped item type gets its own chest(s) only when it needs more than this many slots; smaller piles share a 'misc' chest. Default is one vanilla chest. An item a chest explicitly pins always gets its own home regardless.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 120), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			Gear.Init(config);
		}
	}
	internal struct StackView
	{
		public string Norm;

		public int Count;

		public bool Stackable;

		public string BucketKey;
	}
	internal enum AnchorKind
	{
		None,
		Home,
		Station,
		Sign,
		Pin
	}
	internal sealed class ChestView
	{
		public int Id;

		public string UidKey = "";

		public float Distance;

		public int TotalSlots;

		public int Priority;

		public List<StackView> Stacks;

		public bool ExcludedAsTarget;

		public bool ExcludedAsSource;

		public Dictionary<string, AnchorKind> Anchors;

		public string HomeMarker;

		public bool IsAnchor
		{
			get
			{
				if (Anchors != null)
				{
					return Anchors.Count > 0;
				}
				return false;
			}
		}

		public AnchorKind AnchorFor(string bucket)
		{
			if (Anchors == null || bucket == null)
			{
				return AnchorKind.None;
			}
			if (!Anchors.TryGetValue(bucket, out var value))
			{
				return AnchorKind.None;
			}
			return value;
		}

		public int HeldOf(string norm)
		{
			int num = 0;
			if (Stacks != null)
			{
				for (int i = 0; i < Stacks.Count; i++)
				{
					if (Stacks[i].Norm == norm)
					{
						num += Stacks[i].Count;
					}
				}
			}
			return num;
		}
	}
	internal struct OrganizeMove
	{
		public int SrcId;

		public int SrcStackIndex;

		public int TgtId;

		public string Norm;

		public int Amount;
	}
	internal struct HomeMark
	{
		public int ChestId;

		public string BucketKey;
	}
	internal struct OrganizeSummary
	{
		public int TotalItems;

		public int TargetChests;

		public int SourceChests;

		public int HomelessItems;

		public int BucketsPlanned;
	}
	internal sealed class PlannerInput
	{
		public IReadOnlyList<ChestView> Chests;

		public Func<string, int> MaxStackOf;

		public Func<string, int> BucketRank;

		public Func<int, int, float> DistanceBetween;

		public int MiscPromoteSlots = 24;
	}
	internal sealed class PlannerResult
	{
		public readonly List<OrganizeMove> Moves = new List<OrganizeMove>();

		public readonly List<HomeMark> HomeMarks = new List<HomeMark>();

		public OrganizeSummary Summary;
	}
	internal static class OrganizePlanner
	{
		private struct Holder
		{
			public int ChestId;

			public int StackIndex;

			public int Count;
		}

		private sealed class Reservation
		{
			public int ChestId;

			public AnchorKind Kind;

			public int Priority;

			public float Distance;

			public string UidKey;

			public int Slots;

			public bool Claimed;
		}

		private const int HomeFixedPointCap = 8;

		internal static PlannerResult Plan(PlannerInput input)
		{
			if (input?.Chests == null || input.Chests.Count == 0)
			{
				return new PlannerResult();
			}
			List<ChestView> list = CloneForOverlay(input.Chests, stripHome: false);
			PlannerInput input2 = WithChests(input, list);
			AdoptIncumbentHomes(list, input.MaxStackOf ?? ((Func<string, int>)((string _) => 1)), input.BucketRank ?? ((Func<string, int>)((string _) => 0)), Math.Max(1, input.MiscPromoteSlots));
			PlannerResult plannerResult = null;
			for (int num = 0; num < 8; num++)
			{
				plannerResult = PlanOnce(input2);
				if (plannerResult.HomeMarks.Count == 0)
				{
					break;
				}
				ApplyMarksToOverlay(list, plannerResult.HomeMarks);
				plannerResult = null;
			}
			if (plannerResult == null)
			{
				list = CloneForOverlay(input.Chests, stripHome: true);
				input2 = WithChests(input, list);
				plannerResult = PlanOnce(input2);
				ApplyMarksToOverlay(list, plannerResult.HomeMarks);
			}
			PlannerResult plannerResult2 = new PlannerResult();
			plannerResult2.Moves.AddRange(plannerResult.Moves);
			plannerResult2.Summary = plannerResult.Summary;
			for (int num2 = 0; num2 < input.Chests.Count; num2++)
			{
				ChestView chestView = input.Chests[num2];
				ChestView chestView2 = list[num2];
				if (chestView != null && chestView2 != null)
				{
					string a = (string.IsNullOrEmpty(chestView.HomeMarker) ? null : chestView.HomeMarker);
					string text = (string.IsNullOrEmpty(chestView2.HomeMarker) ? null : chestView2.HomeMarker);
					if (!string.Equals(a, text, StringComparison.Ordinal))
					{
						plannerResult2.HomeMarks.Add(new HomeMark
						{
							ChestId = num2,
							BucketKey = text
						});
					}
				}
			}
			return plannerResult2;
		}

		private static List<ChestView> CloneForOverlay(IReadOnlyList<ChestView> chests, bool stripHome)
		{
			List<ChestView> list = new List<ChestView>(chests.Count);
			for (int i = 0; i < chests.Count; i++)
			{
				ChestView chestView = chests[i];
				if (chestView == null)
				{
					list.Add(null);
					continue;
				}
				Dictionary<string, AnchorKind> dictionary = null;
				if (chestView.Anchors != null)
				{
					foreach (KeyValuePair<string, AnchorKind> anchor in chestView.Anchors)
					{
						if (!stripHome || anchor.Value != AnchorKind.Home)
						{
							if (dictionary == null)
							{
								dictionary = new Dictionary<string, AnchorKind>(StringComparer.Ordinal);
							}
							dictionary[anchor.Key] = anchor.Value;
						}
					}
				}
				list.Add(new ChestView
				{
					Id = chestView.Id,
					UidKey = chestView.UidKey,
					Distance = chestView.Distance,
					TotalSlots = chestView.TotalSlots,
					Priority = chestView.Priority,
					Stacks = chestView.Stacks,
					ExcludedAsTarget = chestView.ExcludedAsTarget,
					ExcludedAsSource = chestView.ExcludedAsSource,
					Anchors = dictionary,
					HomeMarker = (stripHome ? null : chestView.HomeMarker)
				});
			}
			return list;
		}

		private static PlannerInput WithChests(PlannerInput input, List<ChestView> chests)
		{
			return new PlannerInput
			{
				Chests = chests,
				MaxStackOf = input.MaxStackOf,
				BucketRank = input.BucketRank,
				DistanceBetween = input.DistanceBetween,
				MiscPromoteSlots = input.MiscPromoteSlots
			};
		}

		private static void ApplyMarksToOverlay(List<ChestView> overlay, List<HomeMark> marks)
		{
			for (int i = 0; i < marks.Count; i++)
			{
				HomeMark homeMark = marks[i];
				ChestView chestView = overlay[homeMark.ChestId];
				if (chestView == null)
				{
					continue;
				}
				chestView.HomeMarker = homeMark.BucketKey;
				if (chestView.Anchors != null)
				{
					List<string> list = new List<string>();
					foreach (KeyValuePair<string, AnchorKind> anchor in chestView.Anchors)
					{
						if (anchor.Value == AnchorKind.Home && !string.Equals(anchor.Key, homeMark.BucketKey, StringComparison.Ordinal))
						{
							list.Add(anchor.Key);
						}
					}
					foreach (string item in list)
					{
						chestView.Anchors.Remove(item);
					}
					if (chestView.Anchors.Count == 0)
					{
						chestView.Anchors = null;
					}
				}
				if (homeMark.BucketKey != null)
				{
					if (chestView.Anchors == null)
					{
						chestView.Anchors = new Dictionary<string, AnchorKind>(StringComparer.Ordinal);
					}
					if (!chestView.Anchors.TryGetValue(homeMark.BucketKey, out var value) || value < AnchorKind.Home)
					{
						chestView.Anchors[homeMark.BucketKey] = AnchorKind.Home;
					}
				}
			}
		}

		private static void AdoptIncumbentHomes(List<ChestView> overlay, Func<string, int> maxStackOf, Func<string, int> bucketRank, int promoteSlots)
		{
			Dictionary<string, Dictionary<int, int>> dictionary = new Dictionary<string, Dictionary<int, int>>(StringComparer.Ordinal);
			Dictionary<string, Dictionary<string, int>> dictionary2 = new Dictionary<string, Dictionary<string, int>>(StringComparer.Ordinal);
			for (int i = 0; i < overlay.Count; i++)
			{
				ChestView chestView = overlay[i];
				if (chestView?.Stacks == null)
				{
					continue;
				}
				for (int j = 0; j < chestView.Stacks.Count; j++)
				{
					StackView stackView = chestView.Stacks[j];
					if (!string.IsNullOrEmpty(stackView.Norm) && stackView.Count > 0 && !chestView.ExcludedAsSource && !string.IsNullOrEmpty(stackView.BucketKey))
					{
						if (!dictionary.TryGetValue(stackView.BucketKey, out var value))
						{
							value = new Dictionary<int, int>();
							dictionary[stackView.BucketKey] = value;
							dictionary2[stackView.BucketKey] = new Dictionary<string, int>(StringComparer.Ordinal);
						}
						value[i] = (value.TryGetValue(i, out var value2) ? value2 : 0) + stackView.Count;
						Dictionary<string, int> dictionary3 = dictionary2[stackView.BucketKey];
						dictionary3[stackView.Norm] = (dictionary3.TryGetValue(stackView.Norm, out var value3) ? value3 : 0) + stackView.Count;
					}
				}
			}
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, Dictionary<string, int>> item in dictionary2)
			{
				if (!BucketKeys.IsPerType(item.Key) || AnyChestAnchors(overlay, item.Key))
				{
					continue;
				}
				int num = 0;
				foreach (KeyValuePair<string, int> item2 in item.Value)
				{
					num += CeilDiv(item2.Value, Math.Max(1, maxStackOf(item2.Key)));
				}
				if (num <= promoteSlots)
				{
					list.Add(item.Key);
				}
			}
			foreach (string item3 in list)
			{
				if (!dictionary.TryGetValue("misc", out var value4))
				{
					value4 = (dictionary["misc"] = new Dictionary<int, int>());
					dictionary2["misc"] = new Dictionary<string, int>(StringComparer.Ordinal);
				}
				foreach (KeyValuePair<int, int> item4 in dictionary[item3])
				{
					value4[item4.Key] = (value4.TryGetValue(item4.Key, out var value5) ? value5 : 0) + item4.Value;
				}
				Dictionary<string, int> dictionary5 = dictionary2["misc"];
				foreach (KeyValuePair<string, int> item5 in dictionary2[item3])
				{
					dictionary5[item5.Key] = (dictionary5.TryGetValue(item5.Key, out var value6) ? value6 : 0) + item5.Value;
				}
				dictionary.Remove(item3);
				dictionary2.Remove(item3);
			}
			Dictionary<string, int> demandSlots = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, Dictionary<string, int>> item6 in dictionary2)
			{
				int num2 = 0;
				foreach (KeyValuePair<string, int> item7 in item6.Value)
				{
					num2 += CeilDiv(item7.Value, Math.Max(1, maxStackOf(item7.Key)));
				}
				demandSlots[item6.Key] = num2;
			}
			List<string> list2 = new List<string>(dictionary.Keys);
			list2.Sort(delegate(string x, string y)
			{
				int num7 = demandSlots[y].CompareTo(demandSlots[x]);
				if (num7 != 0)
				{
					return num7;
				}
				num7 = bucketRank(x).CompareTo(bucketRank(y));
				return (num7 == 0) ? string.CompareOrdinal(x, y) : num7;
			});
			HashSet<string> hashSet = new HashSet<string>(list2, StringComparer.Ordinal);
			foreach (string item8 in list2)
			{
				if (AnyChestAnchors(overlay, item8))
				{
					continue;
				}
				int num3 = -1;
				int num4 = 0;
				float value7 = 0f;
				string strB = null;
				Dictionary<int, int> dictionary6 = dictionary[item8];
				for (int num5 = 0; num5 < overlay.Count; num5++)
				{
					if (!dictionary6.TryGetValue(num5, out var value8) || value8 <= 0)
					{
						continue;
					}
					ChestView chestView2 = overlay[num5];
					if (chestView2 != null && !chestView2.ExcludedAsTarget && (string.IsNullOrEmpty(chestView2.HomeMarker) || !hashSet.Contains(chestView2.HomeMarker)) && !AnchorsAnotherLiveBucket(chestView2, item8, hashSet))
					{
						bool flag = num3 < 0 || value8 > num4;
						if (!flag && value8 == num4)
						{
							int num6 = chestView2.Distance.CompareTo(value7);
							flag = num6 < 0 || (num6 == 0 && string.CompareOrdinal(chestView2.UidKey ?? "", strB) < 0);
						}
						if (flag)
						{
							num3 = num5;
							num4 = value8;
							value7 = chestView2.Distance;
							strB = chestView2.UidKey ?? "";
						}
					}
				}
				if (num3 >= 0)
				{
					ChestView chestView3 = overlay[num3];
					chestView3.HomeMarker = item8;
					if (chestView3.Anchors == null)
					{
						chestView3.Anchors = new Dictionary<string, AnchorKind>(StringComparer.Ordinal);
					}
					chestView3.Anchors[item8] = AnchorKind.Home;
				}
			}
		}

		private static PlannerResult PlanOnce(PlannerInput input)
		{
			PlannerResult plannerResult = new PlannerResult();
			if (input?.Chests == null || input.Chests.Count == 0)
			{
				return plannerResult;
			}
			IReadOnlyList<ChestView> chests = input.Chests;
			Func<string, int> func = input.MaxStackOf ?? ((Func<string, int>)((string _) => 1));
			Func<string, int> bucketRank = input.BucketRank ?? ((Func<string, int>)((string _) => 0));
			Func<int, int, float> distanceBetween = input.DistanceBetween ?? ((Func<int, int, float>)((int _, int __) => 0f));
			int promoteSlots = Math.Max(1, input.MiscPromoteSlots);
			Dictionary<string, Dictionary<string, List<Holder>>> dictionary = new Dictionary<string, Dictionary<string, List<Holder>>>(StringComparer.Ordinal);
			List<string> list = new List<string>();
			int[] array = new int[chests.Count];
			for (int num = 0; num < chests.Count; num++)
			{
				ChestView chestView = chests[num];
				if (chestView?.Stacks == null)
				{
					continue;
				}
				for (int num2 = 0; num2 < chestView.Stacks.Count; num2++)
				{
					StackView stackView = chestView.Stacks[num2];
					if (string.IsNullOrEmpty(stackView.Norm) || stackView.Count <= 0)
					{
						continue;
					}
					if (chestView.ExcludedAsSource || string.IsNullOrEmpty(stackView.BucketKey))
					{
						array[num]++;
						continue;
					}
					if (!dictionary.TryGetValue(stackView.BucketKey, out var value))
					{
						value = new Dictionary<string, List<Holder>>(StringComparer.Ordinal);
						dictionary[stackView.BucketKey] = value;
						list.Add(stackView.BucketKey);
					}
					if (!value.TryGetValue(stackView.Norm, out var value2))
					{
						value2 = new List<Holder>();
						value[stackView.Norm] = value2;
					}
					value2.Add(new Holder
					{
						ChestId = num,
						StackIndex = num2,
						Count = stackView.Count
					});
				}
			}
			FoldSmallTypeBuckets(chests, dictionary, list, func, promoteSlots);
			Dictionary<string, int> demand = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, Dictionary<string, List<Holder>>> item in dictionary)
			{
				int num3 = 0;
				foreach (KeyValuePair<string, List<Holder>> item2 in item.Value)
				{
					int num4 = 0;
					for (int num5 = 0; num5 < item2.Value.Count; num5++)
					{
						num4 += item2.Value[num5].Count;
					}
					num3 += CeilDiv(num4, Math.Max(1, func(item2.Key)));
				}
				demand[item.Key] = num3;
			}
			int[] array2 = new int[chests.Count];
			for (int num6 = 0; num6 < chests.Count; num6++)
			{
				ChestView chestView2 = chests[num6];
				array2[num6] = ((chestView2 != null && !chestView2.ExcludedAsTarget) ? Math.Max(0, chestView2.TotalSlots - array[num6]) : 0);
			}
			List<string> list2 = new List<string>(demand.Keys);
			list2.Sort(delegate(string a, string b)
			{
				int num10 = demand[b].CompareTo(demand[a]);
				if (num10 != 0)
				{
					return num10;
				}
				num10 = bucketRank(a).CompareTo(bucketRank(b));
				return (num10 == 0) ? string.CompareOrdinal(a, b) : num10;
			});
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (string item3 in list2)
			{
				if (demand[item3] > 0)
				{
					hashSet.Add(item3);
				}
			}
			string[] reservedBy = new string[chests.Count];
			Dictionary<string, List<Reservation>> dictionary2 = new Dictionary<string, List<Reservation>>(StringComparer.Ordinal);
			Dictionary<string, int> dictionary3 = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (string item4 in list2)
			{
				int needed = demand[item4];
				dictionary2[item4] = TakeAnchors(item4, ref needed, chests, array2, reservedBy);
				dictionary3[item4] = needed;
			}
			foreach (string item5 in list2)
			{
				ClaimFree(item5, dictionary3[item5], dictionary2[item5], chests, array2, reservedBy, distanceBetween, hashSet);
			}
			List<OrganizeMove> moves = plannerResult.Moves;
			int num7 = 0;
			foreach (string item6 in list2)
			{
				num7 += DistributeAndDiff(item6, dictionary[item6], dictionary2[item6], chests, array2, func, moves);
			}
			OrderEvictionsFirst(moves);
			EmitHomeMarks(chests, dictionary2, plannerResult.HomeMarks);
			HashSet<int> hashSet2 = new HashSet<int>();
			HashSet<int> hashSet3 = new HashSet<int>();
			int num8 = 0;
			for (int num9 = 0; num9 < moves.Count; num9++)
			{
				num8 += moves[num9].Amount;
				hashSet2.Add(moves[num9].SrcId);
				hashSet3.Add(moves[num9].TgtId);
			}
			plannerResult.Summary = new OrganizeSummary
			{
				TotalItems = num8,
				SourceChests = hashSet2.Count,
				TargetChests = hashSet3.Count,
				HomelessItems = num7,
				BucketsPlanned = list2.Count
			};
			return plannerResult;
		}

		private static void FoldSmallTypeBuckets(IReadOnlyList<ChestView> chests, Dictionary<string, Dictionary<string, List<Holder>>> holdersByBucket, List<string> bucketOrderSeen, Func<string, int> maxStackOf, int promoteSlots)
		{
			List<string> list = new List<string>();
			foreach (string item in bucketOrderSeen)
			{
				if (!BucketKeys.IsPerType(item) || !holdersByBucket.TryGetValue(item, out var value) || AnyChestAnchors(chests, item))
				{
					continue;
				}
				int num = 0;
				foreach (KeyValuePair<string, List<Holder>> item2 in value)
				{
					int num2 = 0;
					for (int i = 0; i < item2.Value.Count; i++)
					{
						num2 += item2.Value[i].Count;
					}
					num += CeilDiv(num2, Math.Max(1, maxStackOf(item2.Key)));
				}
				if (num <= promoteSlots)
				{
					list.Add(item);
				}
			}
			if (list.Count == 0)
			{
				return;
			}
			if (!holdersByBucket.TryGetValue("misc", out var value2))
			{
				value2 = (holdersByBucket["misc"] = new Dictionary<string, List<Holder>>(StringComparer.Ordinal));
				bucketOrderSeen.Add("misc");
			}
			foreach (string item3 in list)
			{
				foreach (KeyValuePair<string, List<Holder>> item4 in holdersByBucket[item3])
				{
					if (!value2.TryGetValue(item4.Key, out var value3))
					{
						value3 = new List<Holder>();
						value2[item4.Key] = value3;
					}
					value3.AddRange(item4.Value);
				}
				holdersByBucket.Remove(item3);
				bucketOrderSeen.Remove(item3);
			}
		}

		private static bool AnyChestAnchors(IReadOnlyList<ChestView> chests, string bucket)
		{
			for (int i = 0; i < chests.Count; i++)
			{
				if (chests[i] != null && chests[i].AnchorFor(bucket) != AnchorKind.None)
				{
					return true;
				}
			}
			return false;
		}

		private static List<Reservation> TakeAnchors(string bucket, ref int needed, IReadOnlyList<ChestView> chests, int[] slotsLeft, string[] reservedBy)
		{
			List<Reservation> list = new List<Reservation>();
			if (needed <= 0)
			{
				return list;
			}
			List<Reservation> list2 = new List<Reservation>();
			for (int i = 0; i < chests.Count; i++)
			{
				ChestView chestView = chests[i];
				if (chestView != null && !chestView.ExcludedAsTarget)
				{
					AnchorKind anchorKind = chestView.AnchorFor(bucket);
					if (anchorKind != AnchorKind.None && anchorKind >= AnchorKind.Station)
					{
						list2.Add(new Reservation
						{
							ChestId = i,
							Kind = anchorKind,
							Priority = chestView.Priority,
							Distance = chestView.Distance,
							UidKey = (chestView.UidKey ?? "")
						});
					}
				}
			}
			list2.Sort(CompareAnchors);
			foreach (Reservation item in list2)
			{
				if (needed <= 0)
				{
					break;
				}
				int num = Math.Min(slotsLeft[item.ChestId], needed);
				if (num > 0)
				{
					slotsLeft[item.ChestId] -= num;
					needed -= num;
					item.Slots = num;
					list.Add(item);
					if (reservedBy[item.ChestId] == null)
					{
						reservedBy[item.ChestId] = bucket;
					}
				}
			}
			return list;
		}

		private static void ClaimFree(string bucket, int needed, List<Reservation> taken, IReadOnlyList<ChestView> chests, int[] slotsLeft, string[] reservedBy, Func<int, int, float> distanceBetween, HashSet<string> liveBuckets)
		{
			while (needed > 0)
			{
				int refChest = ((taken.Count > 0) ? taken[0].ChestId : (-1));
				int num = PickFreeChest(bucket, chests, slotsLeft, reservedBy, refChest, distanceBetween, liveBuckets);
				if (num >= 0)
				{
					int num2 = Math.Min(slotsLeft[num], needed);
					slotsLeft[num] -= num2;
					needed -= num2;
					bool flag = reservedBy[num] == null || string.Equals(reservedBy[num], bucket, StringComparison.Ordinal);
					if (reservedBy[num] == null)
					{
						reservedBy[num] = bucket;
					}
					ChestView chestView = chests[num];
					taken.Add(new Reservation
					{
						ChestId = num,
						Kind = AnchorKind.None,
						Priority = chestView.Priority,
						Distance = chestView.Distance,
						UidKey = (chestView.UidKey ?? ""),
						Slots = num2,
						Claimed = (flag && !AnchorsAnotherLiveBucket(chestView, bucket, liveBuckets))
					});
					continue;
				}
				break;
			}
		}

		private static int CompareAnchors(Reservation a, Reservation b)
		{
			int kind = (int)b.Kind;
			int num = kind.CompareTo((int)a.Kind);
			if (num != 0)
			{
				return num;
			}
			num = b.Priority.CompareTo(a.Priority);
			if (num != 0)
			{
				return num;
			}