Decompiled source of ChestButler v1.1.0

ChestButler.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ChestButler.Core;
using HarmonyLib;
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("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("ChestButler")]
[assembly: AssemblyTitle("ChestButler")]
[assembly: AssemblyVersion("1.1.0.0")]
namespace ChestButler
{
	[BepInPlugin("eksolutions.chestbutler", "ChestButler", "1.1.0")]
	[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 = "1.1.0";

		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<int> OrganizeMovesPerTick;

		internal static ConfigEntry<float> StationRange;

		private Harmony _harmony;

		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_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_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Expected O, but got Unknown
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Expected O, but got Unknown
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Expected O, but got Unknown
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Expected O, but got Unknown
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Expected O, but got Unknown
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Expected O, but got Unknown
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			SorterRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Sorting", "Radius", 32f, new ConfigDescription("Radius (m) around a sorter chest in which target chests are searched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), 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.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 10f), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			StacksPerTick = ((BaseUnityPlugin)this).Config.Bind<int>("Sorting", "StacksPerTick", 2, new ConfigDescription("How many item stacks a sorter moves per tick.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			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
			} }));
			OrganizeMovesPerTick = ((BaseUnityPlugin)this).Config.Bind<int>("Organize", "MovesPerTick", 4, new ConfigDescription("How many item moves the Organize sweep performs per frame (higher = faster, more hitch).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			StationRange = ((BaseUnityPlugin)this).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.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			Groups.Init(((BaseUnityPlugin)this).Config);
			Stations.Init(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("eksolutions.chestbutler");
			_harmony.PatchAll();
			Log.LogInfo((object)"ChestButler 1.1.0 loaded");
		}
	}
}
namespace ChestButler.Patches
{
	[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 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 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;
			EnsureBar(__instance);
			Refresh();
		}

		[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_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Expected O, but got Unknown
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Expected O, but got Unknown
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_024a: Expected O, but got Unknown
			//IL_025c: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Expected O, but got Unknown
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_028c: 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;
				RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
				if (!((Object)(object)val == (Object)null))
				{
					GameObject val2 = new GameObject("psort_bar", new Type[1] { typeof(RectTransform) });
					_bar = (RectTransform)val2.transform;
					((Transform)_bar).SetParent(((Transform)component).parent, false);
					Rect rect = val.rect;
					Rect rect2 = component.rect;
					float width = ((Rect)(ref rect2)).width;
					rect2 = component.rect;
					float height = ((Rect)(ref rect2)).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);
					_bar.anchorMin = new Vector2(0f, 0f);
					_bar.anchorMax = new Vector2(0f, 0f);
					_bar.pivot = new Vector2(0f, 0f);
					_bar.anchoredPosition = new Vector2(num5, num6);
					HorizontalLayoutGroup obj = val2.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 = val2.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 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
				{
					Msg("Chest is empty. Add sample items first");
				}
			}
			else
			{
				bool flag = !Filters.GetManual(_current);
				Filters.SetManual(_current, flag);
				Msg(flag ? "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);
				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))
				{
					OrganizePlan pendingPlan = _pendingPlan;
					ClearPending();
					Organizer.Execute(pendingPlan);
				}
				return;
			}
			OrganizePlan organizePlan = Organizer.BuildPlan(_current, Plugin.SorterRadius.Value);
			if (organizePlan.IsEmpty)
			{
				ClearPending();
				Msg("Nothing to organize");
				return;
			}
			_pendingPlan = organizePlan;
			_pendingChest = _current;
			_pendingAt = Time.time;
			OrganizeSummary summary = organizePlan.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"));
			Msg("Organize: move " + summary.TotalItems + " item" + ((summary.TotalItems == 1) ? "" : "s") + " across " + summary.TargetChests + " chest" + ((summary.TargetChests == 1) ? "" : "s") + " - 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;
				_pinLabel.text = ((count == 0) ? "Pin" : (Filters.GetManual(_current) ? ("Manual (" + count + ")") : ("Auto (" + count + ")")));
				flag4 = count > 0;
				if (flag4)
				{
					_clearLabel.text = "Clear";
				}
				flag5 = Filters.GetSpec(_current).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);
		}

		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);
			}
		}
	}
	[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);
		}
	}
}
namespace ChestButler.Core
{
	internal static class ContainerTracker
	{
		private static readonly HashSet<Container> All = new HashSet<Container>();

		internal static void Register(Container c)
		{
			if ((Object)(object)c != (Object)null)
			{
				All.Add(c);
			}
		}

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

		internal static List<Container> Candidates(Container sorter, float radius, bool excludeSorters = true)
		{
			//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_0087: 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_00bc: Unknown result type (might be due to invalid IL or missing references)
			All.RemoveWhere((Container c) => (Object)(object)c == (Object)null);
			Vector3 position = ((Component)sorter).transform.position;
			List<float> list = new List<float>();
			List<Container> list2 = new List<Container>();
			foreach (Container item in All)
			{
				if (!((Object)(object)item == (Object)(object)sorter) && SorterZdo.HasValidNView(item) && item.GetInventory() != null && !((Object)(object)((Component)item).GetComponentInParent<Piece>() == (Object)null))
				{
					float num = Vector3.Distance(position, ((Component)item).transform.position);
					if (!(num > radius) && (!excludeSorters || !SorterZdo.IsSorter(item)) && SorterZdo.PlayerCanAccess(item) && PrivateArea.CheckAccess(((Component)item).transform.position, 0f, false, true))
					{
						list.Add(num);
						list2.Add(item);
					}
				}
			}
			float[] keys = list.ToArray();
			Container[] array = list2.ToArray();
			Array.Sort(keys, array);
			return new List<Container>(array);
		}

		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 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 = 3f;

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

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

		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);
			}
		}

		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 FilterSpec GetSpec(Container c)
		{
			if (Cache.TryGetValue(c, out var value) && Time.time - value.Key < 3f)
			{
				return value.Value;
			}
			FilterSpec filterSpec = new FilterSpec();
			foreach (string item in GetPinned(c))
			{
				filterSpec.Items.Add(item);
			}
			filterSpec.ManualOnly = GetManual(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_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: 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)
			{
				if (Vector3.Distance(((Component)sign).transform.position, position) > 2.5f || !IsNearestContainerTo(((Component)sign).transform.position, c))
				{
					continue;
				}
				string text = sign.GetText();
				if (string.IsNullOrEmpty(text))
				{
					continue;
				}
				string[] array = text.Split('\n');
				for (int num = 0; num < array.Length; num++)
				{
					string text2 = array[num].Trim();
					if (!text2.ToLowerInvariant().StartsWith("sort:"))
					{
						continue;
					}
					string[] array2 = text2.Substring(5).Split(',');
					for (int num2 = 0; num2 < array2.Length; num2++)
					{
						string text3 = array2[num2].Trim().ToLowerInvariant();
						if (text3.Length == 0)
						{
							continue;
						}
						switch (text3)
						{
						case "off":
						case "ignore":
						case "none":
							spec.Ignore = true;
							continue;
						}
						if (text3.Length >= 2 && text3[0] == 'p' && int.TryParse(text3.Substring(1), out var result))
						{
							spec.Priority = result;
						}
						else if (Groups.IsGroup(text3))
						{
							spec.GroupNames.Add(text3);
						}
						else
						{
							spec.Items.Add(text3);
						}
					}
				}
			}
		}

		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 Groups
	{
		private 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" }
		};

		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>>();

		internal static void Init(ConfigFile config)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			foreach (KeyValuePair<string, string> @default in Defaults)
			{
				Entries[@default.Key] = config.Bind<string>("ItemGroups", @default.Key, @default.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();
			foreach (KeyValuePair<string, ConfigEntry<string>> entry in Entries)
			{
				List<string> list = new List<string>();
				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;
			}
		}

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

		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 Names
	{
		internal static string Normalize(string sharedName)
		{
			if (string.IsNullOrEmpty(sharedName))
			{
				return string.Empty;
			}
			string text = sharedName;
			if (text.StartsWith("$item_"))
			{
				text = text.Substring(6);
			}
			else if (text.StartsWith("$"))
			{
				text = text.Substring(1);
			}
			return text.Replace("_", "").Trim().ToLowerInvariant();
		}

		internal static bool Matches(string token, string normName)
		{
			if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(normName))
			{
				return false;
			}
			bool flag = token[0] == '*';
			bool flag2 = token[token.Length - 1] == '*';
			string text = token.Trim('*');
			if (text.Length == 0)
			{
				return false;
			}
			if (flag && flag2)
			{
				return normName.Contains(text);
			}
			if (flag2)
			{
				return normName.StartsWith(text);
			}
			if (flag)
			{
				return normName.EndsWith(text);
			}
			return normName == text;
		}
	}
	internal struct StackView
	{
		public string Norm;

		public int Count;

		public bool Stackable;
	}
	internal sealed class ChestView
	{
		public int Id;

		public bool ExcludedAsTarget;

		public List<StackView> Stacks;

		public Func<string, bool> PinsItem;

		public Func<string, bool> PinsGroup;

		public Func<string, bool> StationAttracts;

		public int Priority;

		public int EmptySlots;

		public Func<string, int> PartialSpaceFor;

		public Func<string, int> MaxStackOf;

		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 OrganizeSummary
	{
		public int TotalItems;

		public int TargetChests;

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

			public int StackIndex;

			public int Count;
		}

		private enum Tier
		{
			PinItem,
			PinGroup,
			Station,
			Holds
		}

		internal static List<OrganizeMove> Plan(IReadOnlyList<ChestView> chests, out OrganizeSummary summary)
		{
			List<OrganizeMove> list = new List<OrganizeMove>();
			summary = default(OrganizeSummary);
			if (chests == null || chests.Count == 0)
			{
				return list;
			}
			Dictionary<string, List<Holder>> dictionary = new Dictionary<string, List<Holder>>();
			List<string> list2 = new List<string>();
			for (int i = 0; i < chests.Count; i++)
			{
				ChestView chestView = chests[i];
				if (chestView == null || 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 && (stackView.Stackable || AnyTargetPins(chests, stackView.Norm)))
					{
						if (!dictionary.TryGetValue(stackView.Norm, out var value))
						{
							value = new List<Holder>();
							dictionary[stackView.Norm] = value;
							list2.Add(stackView.Norm);
						}
						value.Add(new Holder
						{
							ChestId = i,
							StackIndex = j,
							Count = stackView.Count
						});
					}
				}
			}
			Dictionary<int, int> dictionary2 = new Dictionary<int, int>();
			HashSet<int> hashSet = new HashSet<int>();
			HashSet<int> hashSet2 = new HashSet<int>();
			foreach (string item in list2)
			{
				int num = ChooseTarget(chests, item);
				if (num < 0)
				{
					continue;
				}
				ChestView chestView2 = chests[num];
				if (!dictionary2.ContainsKey(num))
				{
					dictionary2[num] = Math.Max(0, chestView2.EmptySlots);
				}
				int num2 = Math.Max(1, (chestView2.MaxStackOf == null) ? 1 : chestView2.MaxStackOf(item));
				int num3 = Math.Max(0, (chestView2.PartialSpaceFor != null) ? chestView2.PartialSpaceFor(item) : 0);
				int num4 = 0;
				foreach (Holder item2 in dictionary[item])
				{
					if (item2.ChestId != num)
					{
						int val = num3 + num4 + dictionary2[num] * num2;
						int num5 = Math.Min(item2.Count, val);
						if (num5 <= 0)
						{
							break;
						}
						int num6 = num5;
						int num7 = Math.Min(num6, num3);
						num3 -= num7;
						num6 -= num7;
						int num8 = Math.Min(num6, num4);
						num4 -= num8;
						num6 -= num8;
						while (num6 > 0)
						{
							dictionary2[num]--;
							num4 = num2;
							int num9 = Math.Min(num6, num4);
							num4 -= num9;
							num6 -= num9;
						}
						list.Add(new OrganizeMove
						{
							SrcId = item2.ChestId,
							SrcStackIndex = item2.StackIndex,
							TgtId = num,
							Norm = item,
							Amount = num5
						});
						summary.TotalItems += num5;
						hashSet.Add(item2.ChestId);
						hashSet2.Add(num);
					}
				}
			}
			summary.SourceChests = hashSet.Count;
			summary.TargetChests = hashSet2.Count;
			return list;
		}

		private static bool AnyTargetPins(IReadOnlyList<ChestView> chests, string norm)
		{
			for (int i = 0; i < chests.Count; i++)
			{
				ChestView chestView = chests[i];
				if (chestView != null && !chestView.ExcludedAsTarget)
				{
					if (chestView.PinsItem != null && chestView.PinsItem(norm))
					{
						return true;
					}
					if (chestView.PinsGroup != null && chestView.PinsGroup(norm))
					{
						return true;
					}
				}
			}
			return false;
		}

		private static int ChooseTarget(IReadOnlyList<ChestView> chests, string norm)
		{
			int num = PickBest(chests, norm, Tier.PinItem);
			if (num >= 0)
			{
				return num;
			}
			num = PickBest(chests, norm, Tier.PinGroup);
			if (num >= 0)
			{
				return num;
			}
			num = PickBest(chests, norm, Tier.Station);
			if (num >= 0)
			{
				return num;
			}
			return PickBest(chests, norm, Tier.Holds);
		}

		private static int PickBest(IReadOnlyList<ChestView> chests, string norm, Tier tier)
		{
			int result = -1;
			int num = int.MinValue;
			int num2 = -1;
			for (int i = 0; i < chests.Count; i++)
			{
				ChestView chestView = chests[i];
				if (chestView != null && !chestView.ExcludedAsTarget && tier switch
				{
					Tier.PinItem => chestView.PinsItem != null && chestView.PinsItem(norm), 
					Tier.PinGroup => chestView.PinsGroup != null && chestView.PinsGroup(norm), 
					Tier.Station => chestView.StationAttracts != null && chestView.StationAttracts(norm), 
					_ => chestView.HeldOf(norm) > 0, 
				})
				{
					int num3 = chestView.HeldOf(norm);
					if (chestView.Priority > num || (chestView.Priority == num && num3 > num2))
					{
						num = chestView.Priority;
						num2 = num3;
						result = i;
					}
				}
			}
			return result;
		}
	}
	internal sealed class UnityMove
	{
		public Container Source;

		public ItemData Item;

		public Container Target;

		public int Amount;

		public string Norm;
	}
	internal sealed class OrganizePlan
	{
		public readonly List<UnityMove> Moves = new List<UnityMove>();

		public OrganizeSummary Summary;

		public bool IsEmpty => Moves.Count == 0;
	}
	internal static class Organizer
	{
		internal static OrganizePlan BuildPlan(Container origin, float radius)
		{
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			OrganizePlan organizePlan = new OrganizePlan();
			if ((Object)(object)origin == (Object)null)
			{
				return organizePlan;
			}
			List<Container> list = new List<Container> { origin };
			foreach (Container item in ContainerTracker.Candidates(origin, radius, excludeSorters: false))
			{
				if ((Object)(object)item != (Object)(object)origin)
				{
					list.Add(item);
				}
			}
			int count = list.Count;
			List<ItemData>[] array = new List<ItemData>[count];
			List<ChestView> list2 = new List<ChestView>(count);
			Dictionary<string, ItemData> sampleByNorm = new Dictionary<string, ItemData>();
			for (int i = 0; i < count; i++)
			{
				Container val = list[i];
				Inventory inventory = val.GetInventory();
				FilterSpec spec = Filters.GetSpec(val);
				List<string> list3 = Stations.GroupsForChest(val, Plugin.StationRange.Value);
				List<ItemData> list4 = new List<ItemData>();
				List<StackView> list5 = new List<StackView>();
				InventoryBlock val2 = ((inventory != null) ? InventoryBlock.Get(inventory) : null);
				if (inventory != null)
				{
					foreach (ItemData allItem in inventory.GetAllItems())
					{
						if (allItem == null || allItem.m_shared == null || (val2 != null && val2.IsSlotBlocked(allItem.m_gridPos)))
						{
							continue;
						}
						string text = Names.Normalize(allItem.m_shared.m_name);
						if (text.Length != 0)
						{
							list4.Add(allItem);
							list5.Add(new StackView
							{
								Norm = text,
								Count = allItem.m_stack,
								Stackable = (allItem.m_shared.m_maxStackSize > 1)
							});
							if (!sampleByNorm.ContainsKey(text))
							{
								sampleByNorm[text] = allItem;
							}
						}
					}
				}
				array[i] = list4;
				bool excludedAsTarget = SorterZdo.IsSorter(val) || spec.Ignore || spec.ManualOnly;
				FilterSpec specLocal = spec;
				Inventory invLocal = inventory;
				List<string> groupsLocal = list3;
				ItemData value;
				list2.Add(new ChestView
				{
					Id = i,
					ExcludedAsTarget = excludedAsTarget,
					Stacks = list5,
					PinsItem = (string norm) => specLocal.MatchesItem(norm),
					PinsGroup = (string norm) => specLocal.MatchesGroup(norm),
					StationAttracts = (string norm) => StationAttracts(groupsLocal, norm),
					Priority = spec.Priority,
					EmptySlots = ((inventory != null) ? inventory.GetEmptySlots() : 0),
					PartialSpaceFor = delegate(string norm)
					{
						if (invLocal == null)
						{
							return 0;
						}
						return sampleByNorm.TryGetValue(norm, out value) ? invLocal.FindFreeStackSpace(value.m_shared.m_name, (float)value.m_worldLevel) : 0;
					},
					MaxStackOf = (string norm) => (!sampleByNorm.TryGetValue(norm, out value)) ? 1 : value.m_shared.m_maxStackSize
				});
			}
			OrganizeSummary summary;
			List<OrganizeMove> list6 = OrganizePlanner.Plan(list2, out summary);
			organizePlan.Summary = summary;
			foreach (OrganizeMove item2 in list6)
			{
				organizePlan.Moves.Add(new UnityMove
				{
					Source = list[item2.SrcId],
					Item = array[item2.SrcId][item2.SrcStackIndex],
					Target = list[item2.TgtId],
					Amount = item2.Amount,
					Norm = item2.Norm
				});
			}
			return organizePlan;
		}

		private static bool StationAttracts(List<string> groups, string norm)
		{
			if (groups == null)
			{
				return false;
			}
			for (int i = 0; i < groups.Count; i++)
			{
				if (Groups.GroupContains(groups[i], norm))
				{
					return true;
				}
			}
			return false;
		}

		internal static void Execute(OrganizePlan plan)
		{
			if (plan != null && !plan.IsEmpty)
			{
				if ((Object)(object)Plugin.Instance == (Object)null)
				{
					Plugin.Log.LogWarning((object)"[organize] no Plugin.Instance; cannot start execution");
					return;
				}
				Plugin.Log.LogInfo((object)("[organize] executing " + plan.Moves.Count + " move(s)"));
				((MonoBehaviour)Plugin.Instance).StartCoroutine(Run(plan));
			}
		}

		private static IEnumerator Run(OrganizePlan plan)
		{
			int perTick = Mathf.Max(1, Plugin.OrganizeMovesPerTick.Value);
			int budget = perTick;
			int movedItems = 0;
			HashSet<Container> targetsHit = new HashSet<Container>();
			foreach (UnityMove move in plan.Moves)
			{
				Container source = move.Source;
				Container target = move.Target;
				ItemData item = move.Item;
				if ((Object)(object)source == (Object)null || (Object)(object)target == (Object)null || item == null || item.m_shared == null)
				{
					continue;
				}
				Inventory inventory = source.GetInventory();
				Inventory inventory2 = target.GetInventory();
				if (inventory == null || inventory2 == null || !inventory.GetAllItems().Contains(item))
				{
					continue;
				}
				InventoryBlock val = InventoryBlock.Get(inventory);
				if (val != null && val.IsSlotBlocked(item.m_gridPos))
				{
					continue;
				}
				int num = Router.Room(inventory2, item);
				if (num <= 0)
				{
					continue;
				}
				int num2 = Math.Min(move.Amount, Math.Min(item.m_stack, num));
				if (num2 <= 0)
				{
					continue;
				}
				ZNetView val2 = SorterZdo.NView(target);
				if (!((Object)(object)val2 == (Object)null) && val2.IsValid() && !target.IsInUse())
				{
					if (!val2.IsOwner())
					{
						val2.ClaimOwnership();
					}
					ContainerHandler.RemoveItemFromChest(source, item, inventory2, new Vector2i(-1, -1), val2.GetZDO().m_uid, num2, (ItemData)null);
					movedItems += num2;
					targetsHit.Add(target);
					int num3 = budget - 1;
					budget = num3;
					if (num3 <= 0)
					{
						budget = perTick;
						yield return null;
					}
				}
			}
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				((Character)Player.m_localPlayer).Message((MessageType)2, "Organized " + movedItems + " item" + ((movedItems == 1) ? "" : "s") + " into " + targetsHit.Count + " chest" + ((targetsHit.Count == 1) ? "" : "s"), 0, (Sprite)null);
			}
			Plugin.Log.LogInfo((object)("[organize] moved " + movedItems + " items into " + targetsHit.Count + " chests"));
		}
	}
	internal static class Puller
	{
		internal static void PullInto(Container dest, out int movedTotal, out int typesMoved)
		{
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			movedTotal = 0;
			typesMoved = 0;
			FilterSpec spec = Filters.GetSpec(dest);
			if (!spec.HasExplicit)
			{
				return;
			}
			Inventory inventory = dest.GetInventory();
			ZNetView val = SorterZdo.NView(dest);
			if (inventory == null || (Object)(object)val == (Object)null || !val.IsValid())
			{
				return;
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			HashSet<string> hashSet = new HashSet<string>();
			List<Container> list = ContainerTracker.Candidates(dest, Plugin.SorterRadius.Value, excludeSorters: false);
			List<KeyValuePair<int, KeyValuePair<Container, ItemData>>> list2 = new List<KeyValuePair<int, KeyValuePair<Container, ItemData>>>();
			foreach (Container item in list)
			{
				Inventory inventory2 = item.GetInventory();
				InventoryBlock val2 = InventoryBlock.Get(inventory2);
				foreach (ItemData allItem in inventory2.GetAllItems())
				{
					if (allItem != null && allItem.m_shared != null && (val2 == null || !val2.IsSlotBlocked(allItem.m_gridPos)))
					{
						string normName = Names.Normalize(allItem.m_shared.m_name);
						if (spec.MatchesItem(normName) || spec.MatchesGroup(normName))
						{
							int key = inventory2.CountItems(allItem.m_shared.m_name, -1, true);
							list2.Add(new KeyValuePair<int, KeyValuePair<Container, ItemData>>(key, new KeyValuePair<Container, ItemData>(item, allItem)));
						}
					}
				}
			}
			list2.Sort((KeyValuePair<int, KeyValuePair<Container, ItemData>> a, KeyValuePair<int, KeyValuePair<Container, ItemData>> b) => b.Key.CompareTo(a.Key));
			foreach (KeyValuePair<int, KeyValuePair<Container, ItemData>> item2 in list2)
			{
				Container key2 = item2.Value.Key;
				ItemData value = item2.Value.Value;
				string text = Names.Normalize(value.m_shared.m_name);
				int maxStackSize = value.m_shared.m_maxStackSize;
				if (!dictionary.TryGetValue(text, out var value2))
				{
					value2 = ((maxStackSize <= 1) ? 1 : maxStackSize);
				}
				if (value2 <= 0)
				{
					continue;
				}
				int num = Router.Room(inventory, value);
				if (num <= 0)
				{
					continue;
				}
				int num2 = Math.Min(Math.Min(value2, value.m_stack), num);
				if (num2 > 0)
				{
					ContainerHandler.RemoveItemFromChest(key2, value, inventory, new Vector2i(-1, -1), val.GetZDO().m_uid, num2, (ItemData)null);
					dictionary[text] = value2 - num2;
					movedTotal += num2;
					if (hashSet.Add(text))
					{
						typesMoved++;
					}
				}
			}
		}
	}
	internal static class Router
	{
		internal static Container FindTarget(Container sorter, ItemData item, float radius, out int amount)
		{
			amount = 0;
			string normName = Names.Normalize(item.m_shared.m_name);
			bool flag = item.m_shared.m_maxStackSize > 1;
			Container result = null;
			int num = 0;
			int num2 = int.MinValue;
			int num3 = -1;
			foreach (Container item2 in ContainerTracker.Candidates(sorter, radius))
			{
				FilterSpec spec = Filters.GetSpec(item2);
				if (spec.Ignore || spec.ManualOnly)
				{
					continue;
				}
				Inventory inventory = item2.GetInventory();
				int num4 = 0;
				if (spec.MatchesItem(normName))
				{
					num4 = 3;
				}
				else if (spec.MatchesGroup(normName))
				{
					num4 = 2;
				}
				else if (flag && Plugin.ContainsFallback.Value && inventory.HaveItem(item.m_shared.m_name, true))
				{
					num4 = 1;
				}
				if (num4 == 0)
				{
					continue;
				}
				int num5 = Room(inventory, item);
				if (num5 > 0)
				{
					int num6 = inventory.CountItems(item.m_shared.m_name, -1, true);
					if (num4 > num || (num4 == num && (spec.Priority > num2 || (spec.Priority == num2 && num6 > num3))))
					{
						result = item2;
						num = num4;
						num2 = spec.Priority;
						num3 = num6;
						amount = Math.Min(num5, item.m_stack);
					}
				}
			}
			return result;
		}

		internal static int Room(Inventory inv, ItemData item)
		{
			int maxStackSize = item.m_shared.m_maxStackSize;
			if (maxStackSize <= 1)
			{
				return (inv.GetEmptySlots() > 0) ? 1 : 0;
			}
			return inv.FindFreeStackSpace(item.m_shared.m_name, (float)item.m_worldLevel) + inv.GetEmptySlots() * maxStackSize;
		}
	}
	internal class SorterBehaviour : MonoBehaviour
	{
		private Container _container;

		private float _nextTick;

		private void Awake()
		{
			_container = ((Component)this).GetComponent<Container>();
			_nextTick = Time.time + (float)(((Object)this).GetInstanceID() & 0xF) * 0.05f;
		}

		private void FixedUpdate()
		{
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < _nextTick)
			{
				return;
			}
			_nextTick = Time.time + Plugin.TransferInterval.Value;
			if ((Object)(object)_container == (Object)null)
			{
				return;
			}
			ZNetView val = SorterZdo.NView(_container);
			if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner() || !SorterZdo.IsSorter(_container) || _container.IsInUse())
			{
				return;
			}
			Inventory inventory = _container.GetInventory();
			if (inventory == null)
			{
				return;
			}
			InventoryBlock val2 = InventoryBlock.Get(inventory);
			int num = Plugin.StacksPerTick.Value;
			foreach (ItemData item in new List<ItemData>(inventory.GetAllItems()))
			{
				if (num <= 0)
				{
					break;
				}
				if (item != null && item.m_shared != null && (val2 == null || !val2.IsSlotBlocked(item.m_gridPos)))
				{
					int amount;
					Container val3 = Router.FindTarget(_container, item, Plugin.SorterRadius.Value, out amount);
					if (!((Object)(object)val3 == (Object)null) && amount > 0)
					{
						ContainerHandler.AddItemToChest(val3, item, inventory, new Vector2i(-1, -1), val.GetZDO().m_uid, amount);
						num--;
						Plugin.Log.LogDebug((object)$"[sorter] routed {item.m_shared.m_name} x{amount}");
					}
				}
			}
		}
	}
	internal static class SorterZdo
	{
		private static readonly int SorterHash = StringExtensionMethods.GetStableHashCode("psort_sorter");

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

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

		internal static ZNetView NView(Container c)
		{
			if (!Object.op_Implicit((Object)(object)c))
			{
				return null;
			}
			return NViewRef.Invoke(c);
		}

		internal static bool HasValidNView(Container c)
		{
			ZNetView val = NView(c);
			if ((Object)(object)val != (Object)null)
			{
				return val.IsValid();
			}
			return false;
		}

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

		internal static void SetSorter(Container c, bool on)
		{
			ZNetView val = NView(c);
			if (!((Object)(object)val == (Object)null) && val.IsValid())
			{
				if (!val.IsOwner())
				{
					val.ClaimOwnership();
				}
				val.GetZDO().Set(SorterHash, on);
			}
		}

		internal static bool PlayerCanAccess(Container c)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				return CheckAccessFunc(c, localPlayer.GetPlayerID());
			}
			return false;
		}
	}
	internal static class Stations
	{
		private static readonly Dictionary<string, string> Defaults = new Dictionary<string, string>
		{
			{ "$piece_forge", "metals, ores" },
			{ "$piece_workbench", "wood, hides" },
			{ "$piece_stonecutter", "stone" },
			{ "$piece_cauldron", "cooking, meat, seeds" },
			{ "$piece_fermenter", "meads" },
			{ "$piece_blackforge", "metals, valuables" },
			{ "$piece_magetable", "valuables, meads" },
			{ "$piece_smelter", "ores, fuel" },
			{ "$piece_blastfurnace", "ores, fuel" }
		};

		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> Empty = new List<string>();

		private static ConfigEntry<string> Extra;

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

		private static readonly List<Component> Dead = new List<Component>();

		private static readonly FieldInfo AllStationsField = AccessTools.Field(typeof(CraftingStation), "m_allStations");

		private static bool IsReal(Component c)
		{
			ZNetView val = (((Object)(object)c != (Object)null) ? c.GetComponentInParent<ZNetView>() : null);
			if ((Object)(object)val != (Object)null)
			{
				return val.IsValid();
			}
			return false;
		}

		internal static void RegisterProcessor(Component c, string mName)
		{
			if ((Object)(object)c == (Object)null || string.IsNullOrEmpty(mName) || !IsReal(c))
			{
				return;
			}
			foreach (KeyValuePair<Component, string> processor in Processors)
			{
				if ((Object)(object)processor.Key == (Object)null)
				{
					Dead.Add(processor.Key);
				}
			}
			if (Dead.Count > 0)
			{
				foreach (Component item in Dead)
				{
					Processors.Remove(item);
				}
				Dead.Clear();
			}
			Processors[c] = mName;
		}

		internal static void UnregisterProcessor(Component c)
		{
			if ((Object)(object)c != (Object)null)
			{
				Processors.Remove(c);
			}
		}

		internal static void Init(ConfigFile config)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Expected O, but got Unknown
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Expected O, but got Unknown
			foreach (KeyValuePair<string, string> @default in Defaults)
			{
				Entries[@default.Key] = config.Bind<string>("Stations", @default.Key, @default.Value, new ConfigDescription("Comma-separated group names (see [ItemGroups]) this station attracts during Organize.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
				{
					IsAdminOnly = true
				} }));
			}
			Extra = config.Bind<string>("Stations", "CustomStations", "", new ConfigDescription("Extra mappings for modded/unlisted stations. Format: 'token=group1,group2; token2=group3'. Copy a station's token from the log (Info) after pressing Organize, e.g. '$piece_blacksmith=metals,ores'.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} }));
			config.SettingChanged += delegate
			{
				Rebuild();
			};
			Rebuild();
		}

		private static void Rebuild()
		{
			Parsed.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;
			}
			if (Extra == null || string.IsNullOrEmpty(Extra.Value))
			{
				return;
			}
			array = Extra.Value.Split(';');
			foreach (string text2 in array)
			{
				int num = text2.IndexOf('=');
				if (num <= 0)
				{
					continue;
				}
				string text3 = text2.Substring(0, num).Trim();
				if (text3.Length == 0)
				{
					continue;
				}
				List<string> list2 = new List<string>();
				string[] array2 = text2.Substring(num + 1).Split(',');
				for (int j = 0; j < array2.Length; j++)
				{
					string text4 = array2[j].Trim().ToLowerInvariant();
					if (text4.Length > 0)
					{
						list2.Add(text4);
					}
				}
				if (list2.Count > 0)
				{
					Parsed[text3] = list2;
				}
			}
		}

		internal static List<string> GroupsForStationName(string mName)
		{
			if (!string.IsNullOrEmpty(mName) && Parsed.TryGetValue(mName, out var value))
			{
				return value;
			}
			return Empty;
		}

		internal static List<string> GroupsForChest(Container c, float range)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)c == (Object)null)
			{
				return Empty;
			}
			Vector3 position = ((Component)c).transform.position;
			string bestMapped = null;
			string bestUnmapped = null;
			float bestMappedD = range;
			float bestUnmappedD = range;
			List<CraftingStation> list = ((AllStationsField != null) ? (AllStationsField.GetValue(null) as List<CraftingStation>) : null);
			if (list != null)
			{
				foreach (CraftingStation item in list)
				{
					if (!((Object)(object)item == (Object)null) && IsReal((Component)(object)item))
					{
						Consider(item.m_name, Vector3.Distance(((Component)item).transform.position, position), ref bestMapped, ref bestMappedD, ref bestUnmapped, ref bestUnmappedD);
					}
				}
			}
			foreach (KeyValuePair<Component, string> processor in Processors)
			{
				if ((Object)(object)processor.Key == (Object)null)
				{
					Dead.Add(processor.Key);
				}
				else if (IsReal(processor.Key))
				{
					Consider(processor.Value, Vector3.Distance(processor.Key.transform.position, position), ref bestMapped, ref bestMappedD, ref bestUnmapped, ref bestUnmappedD);
				}
			}
			if (Dead.Count > 0)
			{
				foreach (Component item2 in Dead)
				{
					Processors.Remove(item2);
				}
				Dead.Clear();
			}
			if (bestMapped != null)
			{
				List<string> list2 = GroupsForStationName(bestMapped);
				Plugin.Log.LogInfo((object)("[organize] chest near '" + bestMapped + "' (" + bestMappedD.ToString("0.0") + "m) -> " + string.Join(",", list2)));
				return list2;
			}
			if (bestUnmapped != null)
			{
				Plugin.Log.LogInfo((object)("[organize] chest near station '" + bestUnmapped + "' (" + bestUnmappedD.ToString("0.0") + "m) has NO [Stations] mapping - add '" + bestUnmapped + " = <groups>' to the config to route its materials"));
			}
			return Empty;
		}

		private static void Consider(string mName, float d, ref string bestMapped, ref float bestMappedD, ref string bestUnmapped, ref float bestUnmappedD)
		{
			if (string.IsNullOrEmpty(mName))
			{
				return;
			}
			if (Parsed.TryGetValue(mName, out var value) && value.Count > 0)
			{
				if (d < bestMappedD)
				{
					bestMappedD = d;
					bestMapped = mName;
				}
			}
			else if (d < bestUnmappedD)
			{
				bestUnmappedD = d;
				bestUnmapped = mName;
			}
		}
	}
}