Decompiled source of DvergrTidier v1.2.5

DvergrSorter.dll

Decompiled 12 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Collections;
using Unity.Jobs;
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.6.2", FrameworkDisplayName = "")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DvergrSorter
{
	internal static class ChestLock
	{
		[HarmonyPatch(typeof(Container), "GetHoverText")]
		private static class Container_GetHoverText
		{
			private static void Postfix(Container __instance, ref string __result)
			{
				if (IsLocked(__instance))
				{
					List<string> list = Filter(__instance).Select(Localization.L).ToList();
					string text = ((list.Count <= 4) ? string.Join(", ", list) : (string.Join(", ", list.Take(4)) + $" +{list.Count - 4}"));
					__result = __result + "\n<color=orange>Locked. Sorted here: " + ((list.Count == 0) ? "nothing" : text) + "</color>";
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "Awake")]
		private static class InventoryGui_Awake
		{
			private static void Postfix(InventoryGui __instance)
			{
				try
				{
					CreateButton(__instance);
				}
				catch (Exception arg)
				{
					DvergrSorterPlugin.Log.LogError((object)$"Couldn't add the lock button: {arg}");
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "Update")]
		private static class InventoryGui_Update
		{
			private static void Postfix(InventoryGui __instance)
			{
				//IL_003d: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)_button == (Object)null)
				{
					return;
				}
				Container val = CurrentContainer.Invoke(__instance);
				if ((Object)(object)val != (Object)(object)_rangeCheckedFor)
				{
					_rangeCheckedFor = val;
					_inRange = (Object)(object)val != (Object)null && SortingBell.AnyPaymentChestNear(((Component)val).transform.position);
				}
				bool flag = (Object)(object)val != (Object)null && _inRange && __instance.IsContainerOpen() && (Object)(object)((Component)val).GetComponent<Piece>() != (Object)null;
				if (((Component)_button).gameObject.activeSelf != flag)
				{
					((Component)_button).gameObject.SetActive(flag);
				}
				if (!flag)
				{
					_shownFor = null;
					return;
				}
				bool flag2 = IsLocked(val);
				if (_shownLocked != flag2 || !((Object)(object)_shownFor == (Object)(object)val))
				{
					_shownLocked = flag2;
					_shownFor = val;
					if ((Object)(object)_icon != (Object)null)
					{
						_icon.sprite = (flag2 ? _lockedSprite : _unlockedSprite);
					}
				}
			}
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static Func<string, string> <0>__L;

			public static UnityAction <1>__OnClick;
		}

		private const string ZdoLocked = "dsort_locked";

		private const string ZdoFilter = "dsort_filter";

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

		private static readonly FieldRef<InventoryGui, Container> CurrentContainer = AccessTools.FieldRefAccess<InventoryGui, Container>("m_currentContainer");

		private static Button _button;

		private static Image _icon;

		private static Sprite _lockedSprite;

		private static Sprite _unlockedSprite;

		private static bool? _shownLocked;

		private static Container _shownFor;

		private static Container _rangeCheckedFor;

		private static bool _inRange;

		private static string _lockedIcon;

		private static string _unlockedIcon;

		private static string LockedIcon
		{
			get
			{
				object obj = _lockedIcon;
				if (obj == null)
				{
					obj = Icon("locked", "lockedWithKey") ?? "[L]";
					_lockedIcon = (string)obj;
				}
				return (string)obj;
			}
		}

		private static string UnlockedIcon
		{
			get
			{
				object obj = _unlockedIcon;
				if (obj == null)
				{
					obj = Icon("unlock", "unlocked", "openLock") ?? "[ ]";
					_unlockedIcon = (string)obj;
				}
				return (string)obj;
			}
		}

		private static ZDO Zdo(Container c)
		{
			ZNetView val = (((Object)(object)c != (Object)null) ? ContainerNview.Invoke(c) : null);
			if (!((Object)(object)val != (Object)null) || !val.IsValid())
			{
				return null;
			}
			return val.GetZDO();
		}

		public static bool IsLocked(Container c)
		{
			ZDO obj = Zdo(c);
			if (obj == null)
			{
				return false;
			}
			return obj.GetBool("dsort_locked", false);
		}

		public static HashSet<string> Filter(Container c)
		{
			ZDO obj = Zdo(c);
			return new HashSet<string>((((obj != null) ? obj.GetString("dsort_filter", "") : null) ?? "").Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries));
		}

		public static int SetLocked(Container c, bool locked)
		{
			ZNetView val = ContainerNview.Invoke(c);
			if ((Object)(object)val == (Object)null || !val.IsValid())
			{
				return 0;
			}
			if (!val.IsOwner())
			{
				val.ClaimOwnership();
			}
			ZDO zDO = val.GetZDO();
			List<string> list = (locked ? (from i in c.GetInventory().GetAllItems()
				select i.m_shared.m_name).Distinct().ToList() : new List<string>());
			zDO.Set("dsort_locked", locked);
			zDO.Set("dsort_filter", string.Join("\n", list));
			DvergrSorterPlugin.Log.LogInfo((object)((locked ? "Locked" : "Unlocked") + " " + ChestSorter.Describe(c) + (locked ? (": " + string.Join(", ", list.Select(Localization.L))) : "")));
			return list.Count;
		}

		private static Sprite MakePadlock(bool locked)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_005d: 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_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(32, 32, (TextureFormat)4, false)
			{
				filterMode = (FilterMode)0,
				wrapMode = (TextureWrapMode)1
			};
			Color val2 = default(Color);
			((Color)(ref val2))..ctor(0f, 0f, 0f, 0f);
			Color val3 = (locked ? new Color(1f, 0.72f, 0.2f) : new Color(0.85f, 0.85f, 0.85f));
			Color val4 = (locked ? new Color(0.45f, 0.25f, 0.05f) : new Color(0.3f, 0.3f, 0.3f));
			Color c = default(Color);
			((Color)(ref c))..ctor(0.12f, 0.08f, 0.04f);
			Color[] px = (Color[])(object)new Color[1024];
			for (int i = 0; i < px.Length; i++)
			{
				px[i] = val2;
			}
			int num = ((!locked) ? 6 : 0);
			int num2 = 16;
			int num3 = 18 + num;
			for (int j = 0; j < 32; j++)
			{
				for (int k = 0; k < 32; k++)
				{
					float num4 = Mathf.Sqrt(((float)(k - num2) + 0.5f) * ((float)(k - num2) + 0.5f) + ((float)(j - num3) + 0.5f) * ((float)(j - num3) + 0.5f));
					bool num5 = j >= num3 && num4 <= 9f && num4 >= 6f;
					bool flag = k >= num2 - 9 && k < num2 - 6 && j >= 15 && j < num3;
					bool flag2 = k >= num2 + 6 && k < num2 + 9 && j >= 15 && j < num3 && locked;
					if (num5 || flag || flag2)
					{
						Put(k, j, val4);
					}
				}
			}
			for (int l = 2; l <= 17; l++)
			{
				for (int m = 5; m <= 26; m++)
				{
					Put(m, l, (m == 5 || m == 26 || l == 2 || l == 17) ? val4 : val3);
				}
			}
			for (int n = 9; n <= 13; n++)
			{
				for (int num6 = 14; num6 <= 17; num6++)
				{
					if (((float)num6 - 15.5f) * ((float)num6 - 15.5f) + ((float)n - 11f) * ((float)n - 11f) <= 4.5f)
					{
						Put(num6, n, c);
					}
				}
			}
			for (int num7 = 5; num7 <= 9; num7++)
			{
				for (int num8 = 15; num8 <= 16; num8++)
				{
					Put(num8, num7, c);
				}
			}
			val.SetPixels(px);
			val.Apply();
			return Sprite.Create(val, new Rect(0f, 0f, 32f, 32f), new Vector2(0.5f, 0.5f), 32f);
			void Put(int x, int y, Color val5)
			{
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				if (x >= 0 && x < 32 && y >= 0 && y < 32)
				{
					px[y * 32 + x] = val5;
				}
			}
		}

		private static string Icon(params string[] names)
		{
			TMP_SpriteAsset defaultSpriteAsset = TMP_Settings.defaultSpriteAsset;
			if ((Object)(object)defaultSpriteAsset == (Object)null)
			{
				return null;
			}
			List<TMP_SpriteAsset> list = new List<TMP_SpriteAsset> { defaultSpriteAsset };
			if (defaultSpriteAsset.fallbackSpriteAssets != null)
			{
				list.AddRange(defaultSpriteAsset.fallbackSpriteAssets.Where((TMP_SpriteAsset a) => (Object)(object)a != (Object)null));
			}
			foreach (string n in names)
			{
				if (list.Any((TMP_SpriteAsset a) => a.GetSpriteIndexFromName(n) >= 0))
				{
					return "<sprite name=\"" + n + "\">";
				}
			}
			return null;
		}

		private static void CreateButton(InventoryGui gui)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Expected O, but got Unknown
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Expected O, but got Unknown
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Expected O, but got Unknown
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			Button takeAllButton = gui.m_takeAllButton;
			if (!((Object)(object)takeAllButton == (Object)null))
			{
				GameObject val = Object.Instantiate<GameObject>(((Component)takeAllButton).gameObject, ((Component)takeAllButton).transform.parent, false);
				((Object)val).name = "dsort_LockButton";
				_button = val.GetComponent<Button>();
				_button.onClick = new ButtonClickedEvent();
				ButtonClickedEvent onClick = _button.onClick;
				object obj = <>O.<1>__OnClick;
				if (obj == null)
				{
					UnityAction val2 = OnClick;
					<>O.<1>__OnClick = val2;
					obj = (object)val2;
				}
				((UnityEvent)onClick).AddListener((UnityAction)obj);
				TMP_Text[] componentsInChildren = val.GetComponentsInChildren<TMP_Text>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					((Component)componentsInChildren[i]).gameObject.SetActive(false);
				}
				_lockedSprite = MakePadlock(locked: true);
				_unlockedSprite = MakePadlock(locked: false);
				GameObject val3 = new GameObject("dsort_LockIcon", new Type[2]
				{
					typeof(RectTransform),
					typeof(Image)
				});
				val3.transform.SetParent(val.transform, false);
				RectTransform val4 = (RectTransform)val3.transform;
				Vector2 val5 = default(Vector2);
				((Vector2)(ref val5))..ctor(0.5f, 0.5f);
				val4.anchorMax = val5;
				val4.anchorMin = val5;
				val4.pivot = new Vector2(0.5f, 0.5f);
				val4.anchoredPosition = Vector2.zero;
				_icon = val3.GetComponent<Image>();
				_icon.preserveAspect = true;
				((Graphic)_icon).raycastTarget = false;
				_icon.sprite = _unlockedSprite;
				RectTransform val6 = (RectTransform)((Component)takeAllButton).transform;
				RectTransform val7 = (RectTransform)val.transform;
				Rect rect = val6.rect;
				float num;
				if (!(((Rect)(ref rect)).height > 1f))
				{
					num = 46f;
				}
				else
				{
					rect = val6.rect;
					num = ((Rect)(ref rect)).height;
				}
				float num2 = num;
				val7.sizeDelta = new Vector2(num2 * 1.15f, val6.sizeDelta.y);
				Vector2 anchoredPosition = val6.anchoredPosition;
				rect = val6.rect;
				val7.anchoredPosition = anchoredPosition + new Vector2(((Rect)(ref rect)).width * (1f - val6.pivot.x) + val7.sizeDelta.x * val7.pivot.x + 8f, 0f);
				val4.sizeDelta = new Vector2(num2 * 0.7f, num2 * 0.7f);
				_shownLocked = null;
			}
		}

		private static void OnClick()
		{
			InventoryGui instance = InventoryGui.instance;
			Container val = (((Object)(object)instance != (Object)null) ? CurrentContainer.Invoke(instance) : null);
			if (!((Object)(object)val == (Object)null))
			{
				bool flag = !IsLocked(val);
				int num = SetLocked(val, flag);
				_shownLocked = null;
				Player localPlayer = Player.m_localPlayer;
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)2, (!flag) ? "Unlocked: back to automatic sorting" : ((num == 0) ? "Locked (empty): the dvergr won't put anything here" : string.Format("Locked: the dvergr keeps {0} item type{1} here", num, (num == 1) ? "" : "s")), 0, (Sprite)null, false);
				}
			}
		}
	}
	internal class SortTask
	{
		public Container From;

		public Container To;

		public string Group;

		public ChestSorter.Key Key;

		public bool Done;
	}
	internal class SortPlan
	{
		public readonly List<Container> Chests;

		public readonly Dictionary<string, List<Container>> Homes = new Dictionary<string, List<Container>>();

		public readonly Dictionary<Container, string> Owner = new Dictionary<Container, string>();

		public readonly List<SortTask> Tasks = new List<SortTask>();

		public readonly HashSet<Container> Locked = new HashSet<Container>();

		public bool HomesChanged;

		public const string LockedOwner = "#locked";

		public SortPlan(List<Container> chests)
		{
			Chests = chests;
		}

		public void Assign(Container c, string group)
		{
			Owner[c] = group;
			if (!Homes.TryGetValue(group, out var value))
			{
				value = (Homes[group] = new List<Container>());
			}
			if (!value.Contains(c))
			{
				value.Add(c);
			}
			HomesChanged = true;
		}
	}
	internal static class ChestSorter
	{
		public struct Key : IEquatable<Key>
		{
			public string Name;

			public int Quality;

			public int WorldLevel;

			public bool Equals(Key o)
			{
				if (Name == o.Name && Quality == o.Quality)
				{
					return WorldLevel == o.WorldLevel;
				}
				return false;
			}

			public override bool Equals(object o)
			{
				if (o is Key o2)
				{
					return Equals(o2);
				}
				return false;
			}

			public override int GetHashCode()
			{
				return (Name?.GetHashCode() ?? 0) ^ (Quality * 397) ^ (WorldLevel * 7919);
			}

			public override string ToString()
			{
				if (Quality <= 1)
				{
					return Localization.L(Name);
				}
				return $"{Localization.L(Name)} *{Quality}";
			}
		}

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

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

		public static Key KeyOf(ItemData item)
		{
			return new Key
			{
				Name = item.m_shared.m_name,
				Quality = item.m_quality,
				WorldLevel = item.m_worldLevel
			};
		}

		public static string GroupOf(ItemData item)
		{
			return ItemGroups.Of(item);
		}

		public static string HomeKey(SortPlan plan, ItemData item)
		{
			string text = "=" + item.m_shared.m_name;
			if (!plan.Homes.ContainsKey(text))
			{
				return GroupOf(item);
			}
			return text;
		}

		public static List<Container> FindContainers(Vector3 center, float radius)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			List<Container> list = new List<Container>();
			Player localPlayer = Player.m_localPlayer;
			long num = (((Object)(object)localPlayer != (Object)null) ? localPlayer.GetPlayerID() : 0);
			Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
			foreach (Container val in array)
			{
				if (!(Vector3.Distance(center, ((Component)val).transform.position) > radius))
				{
					ZNetView val2 = ContainerNview.Invoke(val);
					if (!((Object)(object)val2 == (Object)null) && val2.IsValid() && !((Object)(object)((Component)val).GetComponent<Piece>() == (Object)null) && !((Object)(object)val.m_wagon != (Object)null) && !((Object)(object)((Component)val).GetComponentInParent<Ship>() != (Object)null) && !((Object)(object)((Component)val).GetComponent<TombStone>() != (Object)null) && val.GetInventory() != null && (num == 0L || ContainerCheckAccess(val, num)) && PrivateArea.CheckAccess(((Component)val).transform.position, 0f, false, false) && !HasNoSortSign(val))
					{
						list.Add(val);
					}
				}
			}
			return list;
		}

		private static bool HasNoSortSign(Container c)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Collider[] array = Physics.OverlapSphere(((Component)c).transform.position, 1.5f);
			for (int i = 0; i < array.Length; i++)
			{
				Sign componentInParent = ((Component)array[i]).GetComponentInParent<Sign>();
				if ((Object)(object)componentInParent != (Object)null && (componentInParent.GetText() ?? "").IndexOf("#nosort", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}

		public static SortPlan Plan(List<Container> chests, IDictionary<string, string> saved = null)
		{
			ItemGroups.EnsureLoaded();
			SortPlan plan = new SortPlan(chests);
			HashSet<string> hashSet = Blocklist();
			foreach (Container item in chests.Where(ChestLock.IsLocked))
			{
				plan.Locked.Add(item);
				plan.Owner[item] = "#locked";
				foreach (string item2 in ChestLock.Filter(item))
				{
					AddHome(plan, "=" + item2, item);
				}
			}
			Dictionary<string, Dictionary<Container, int>> dictionary = new Dictionary<string, Dictionary<Container, int>>();
			foreach (Container item3 in chests.Where((Container item) => !plan.Locked.Contains(item)))
			{
				foreach (ItemData allItem in item3.GetInventory().GetAllItems())
				{
					if (!hashSet.Contains(allItem.m_shared.m_name) && !plan.Homes.ContainsKey("=" + allItem.m_shared.m_name))
					{
						string key = GroupOf(allItem);
						if (!dictionary.TryGetValue(key, out var value))
						{
							value = (dictionary[key] = new Dictionary<Container, int>());
						}
						value.TryGetValue(item3, out var value2);
						value[item3] = value2 + 1;
					}
				}
			}
			if (saved != null)
			{
				foreach (Container item4 in chests.Where((Container item) => !plan.Locked.Contains(item)))
				{
					if (saved.TryGetValue(ReachCache.Id(item4), out var value3))
					{
						plan.Owner[item4] = value3;
						AddHome(plan, value3, item4);
					}
				}
			}
			plan.HomesChanged = false;
			foreach (var (text, val, _) in from t in dictionary.SelectMany((KeyValuePair<string, Dictionary<Container, int>> kv) => kv.Value.Select((KeyValuePair<Container, int> p) => (kv.Key, p.Key, Value: p.Value)))
				orderby t.Value descending
				select t)
			{
				if (!plan.Homes.ContainsKey(text) && !plan.Owner.ContainsKey(val))
				{
					plan.Assign(val, text);
				}
			}
			foreach (Container c in chests.Where((Container val3) => !plan.Owner.ContainsKey(val3) && val3.GetInventory().GetAllItems().Count > 0))
			{
				string text2 = (from kv in dictionary
					where kv.Value.ContainsKey(c)
					orderby kv.Value[c] descending
					select kv.Key).FirstOrDefault();
				if (text2 != null)
				{
					plan.Assign(c, text2);
				}
			}
			foreach (KeyValuePair<string, Dictionary<Container, int>> item5 in from kv in dictionary
				where !plan.Homes.ContainsKey(kv.Key)
				orderby kv.Value.Values.Sum() descending
				select kv)
			{
				Container val2 = ((IEnumerable<Container>)chests).FirstOrDefault((Func<Container, bool>)((Container val3) => !plan.Owner.ContainsKey(val3) && val3.GetInventory().GetAllItems().Count == 0));
				if ((Object)(object)val2 != (Object)null)
				{
					plan.Assign(val2, item5.Key);
					continue;
				}
				AddHome(plan, item5.Key, item5.Value.OrderByDescending((KeyValuePair<Container, int> p) => p.Value).First().Key);
			}
			foreach (Container chest in chests)
			{
				foreach (ItemData item6 in from g in Foreign(plan, chest).GroupBy(KeyOf)
					select g.First())
				{
					string text3 = HomeKey(plan, item6);
					plan.Tasks.Add(new SortTask
					{
						From = chest,
						To = plan.Homes[text3][0],
						Group = text3,
						Key = KeyOf(item6)
					});
				}
			}
			return plan;
		}

		private static void AddHome(SortPlan plan, string group, Container c)
		{
			if (!plan.Homes.TryGetValue(group, out var value))
			{
				value = (plan.Homes[group] = new List<Container>());
			}
			if (!value.Contains(c))
			{
				value.Add(c);
			}
		}

		public static int Execute(SortPlan plan, SortTask task, List<string> dryRunLog = null)
		{
			task.Done = true;
			if (dryRunLog != null)
			{
				int num = (from i in task.From.GetInventory().GetAllItems()
					where KeyOf(i).Equals(task.Key)
					select i).Sum((ItemData i) => i.m_stack);
				dryRunLog.Add($"{num}x {task.Key} [{task.Group}]: {Describe(task.From)} -> {Describe(task.To)}");
				return 1;
			}
			if (!Claim(task.From))
			{
				return 0;
			}
			int num2 = 0;
			foreach (ItemData item in (from i in task.From.GetInventory().GetAllItems()
				where KeyOf(i).Equals(task.Key)
				select i).ToList())
			{
				int num3 = item.m_stack;
				int num4 = 0;
				while (num3 > 0)
				{
					List<Container> list = plan.Homes[task.Group];
					Container val = ((num4 < list.Count) ? list[num4] : AddOverflowHome(plan, task.Group, task.From));
					if ((Object)(object)val == (Object)null)
					{
						break;
					}
					if (!((Object)(object)val == (Object)(object)task.From) && Claim(val))
					{
						int num5 = Math.Min(num3, MaxFit(val.GetInventory(), item));
						if (num5 > 0)
						{
							if (!Transfer(task.From.GetInventory(), val.GetInventory(), item, num5))
							{
								break;
							}
							num3 -= num5;
							num2++;
						}
					}
					num4++;
				}
			}
			return num2;
		}

		public static (int moved, int tasks) SortInstant(List<Container> chests, List<string> dryRunLog = null, IDictionary<string, string> saved = null)
		{
			SortPlan sortPlan = Plan(chests, saved);
			int num = 0;
			foreach (SortTask task in sortPlan.Tasks)
			{
				num += Execute(sortPlan, task, dryRunLog);
			}
			return (moved: num, tasks: sortPlan.Tasks.Count);
		}

		public static List<ItemData> Foreign(SortPlan plan, Container c)
		{
			HashSet<string> blocked = Blocklist();
			bool locked = plan.Locked.Contains(c) || ChestLock.IsLocked(c);
			HashSet<string> filter = (locked ? ChestLock.Filter(c) : null);
			List<Container> value;
			return (from i in c.GetInventory().GetAllItems()
				where !blocked.Contains(i.m_shared.m_name) && (!locked || !filter.Contains(i.m_shared.m_name)) && plan.Homes.TryGetValue(HomeKey(plan, i), out value) && !value.Contains(c)
				select i).ToList();
		}

		public static bool IsHomeOf(SortPlan plan, string group, Container c)
		{
			if (plan.Homes.TryGetValue(group, out var value))
			{
				return value.Contains(c);
			}
			return false;
		}

		public static int Deposit(Container target, Inventory carry, Func<ItemData, bool> filter)
		{
			if (!Claim(target))
			{
				return 0;
			}
			int num = 0;
			foreach (ItemData item in carry.GetAllItems().Where(filter).ToList())
			{
				int num2 = MaxFit(target.GetInventory(), item);
				if (num2 > 0 && Transfer(carry, target.GetInventory(), item, num2))
				{
					num++;
				}
			}
			return num;
		}

		public static int Take(Container source, Inventory carry, ItemData item, int amount)
		{
			if (!Claim(source))
			{
				return 0;
			}
			amount = Math.Min(amount, MaxFit(carry, item));
			if (amount <= 0 || !Transfer(source.GetInventory(), carry, item, amount))
			{
				return 0;
			}
			return amount;
		}

		public static int RoomAtHomes(SortPlan plan, ItemData item, Inventory carry, Func<Container, bool> usable = null)
		{
			if (!plan.Homes.TryGetValue(HomeKey(plan, item), out var value))
			{
				return 0;
			}
			int num = value.Where((Container h) => (Object)(object)h != (Object)null && (usable == null || usable(h))).Sum((Container h) => MaxFit(h.GetInventory(), item));
			if (carry == null)
			{
				return num;
			}
			Key key = KeyOf(item);
			int num2 = (from i in carry.GetAllItems()
				where KeyOf(i).Equals(key)
				select i).Sum((ItemData i) => i.m_stack);
			return Math.Max(0, num - num2);
		}

		public static Container AddOverflowHome(SortPlan plan, string group, Container exclude = null)
		{
			if (group.StartsWith("="))
			{
				return null;
			}
			Container val = ((IEnumerable<Container>)plan.Chests).FirstOrDefault((Func<Container, bool>)((Container x) => (Object)(object)x != (Object)(object)exclude && !plan.Owner.ContainsKey(x) && x.GetInventory().GetAllItems().Count == 0));
			if ((Object)(object)val != (Object)null)
			{
				plan.Assign(val, group);
			}
			return val;
		}

		public static bool HasRoomFor(Container c, ItemData item)
		{
			return MaxFit(c.GetInventory(), item) > 0;
		}

		private static int MaxFit(Inventory target, ItemData item)
		{
			if (target.CanAddItem(item, item.m_stack))
			{
				return item.m_stack;
			}
			int num = 0;
			int num2 = item.m_stack - 1;
			while (num < num2)
			{
				int num3 = (num + num2 + 1) / 2;
				if (target.CanAddItem(item, num3))
				{
					num = num3;
				}
				else
				{
					num2 = num3 - 1;
				}
			}
			return num;
		}

		private static bool Transfer(Inventory from, Inventory to, ItemData item, int amount)
		{
			try
			{
				ItemData val = item.Clone();
				val.m_stack = amount;
				if (!to.AddItem(val))
				{
					return false;
				}
				if (!from.RemoveItem(item, amount))
				{
					to.RemoveItem(item.m_shared.m_name, amount, item.m_quality, false);
					return false;
				}
				return true;
			}
			catch (Exception arg)
			{
				DvergrSorterPlugin.Log.LogError((object)$"Transfer of {item?.m_shared?.m_name} failed: {arg}");
				return false;
			}
		}

		internal static bool Claim(Container c)
		{
			if ((Object)(object)c == (Object)null)
			{
				return false;
			}
			ZNetView val = ContainerNview.Invoke(c);
			if ((Object)(object)val == (Object)null || !val.IsValid() || c.IsInUse())
			{
				return false;
			}
			if (!val.IsOwner())
			{
				val.ClaimOwnership();
			}
			return val.IsOwner();
		}

		private static HashSet<string> Blocklist()
		{
			return new HashSet<string>(from s in DvergrSorterPlugin.ItemBlocklist.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select s.Trim());
		}

		internal static string Describe(Container c)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			return $"{Localization.L(c.m_name)}@{((Component)c).transform.position.x:F0},{((Component)c).transform.position.y:F0},{((Component)c).transform.position.z:F0}";
		}
	}
	[BepInPlugin("com.chase.dvergrsorter", "DvergrSorter", "1.2.5")]
	public class DvergrSorterPlugin : BaseUnityPlugin
	{
		public const string Guid = "com.chase.dvergrsorter";

		public const string ModName = "DvergrSorter";

		public const string Version = "1.2.5";

		internal static ManualLogSource Log;

		internal static ConfigEntry<float> SortRadius;

		internal static ConfigEntry<bool> WalkToChests;

		internal static ConfigEntry<bool> CloseDoorsBehind;

		internal static ConfigEntry<float> UnreachableRetryMinutes;

		internal static ConfigEntry<float> RescanSeconds;

		internal static ConfigEntry<int> CoinsPerStack;

		internal static ConfigEntry<float> HeldItemScale;

		internal static ConfigEntry<string> Recipe;

		internal static ConfigEntry<string> CraftingStation;

		internal static ConfigEntry<string> ItemBlocklist;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Expected O, but got Unknown
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Expected O, but got Unknown
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0172: Expected O, but got Unknown
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Game.isModded = true;
			SortRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SortRadius", 20f, new ConfigDescription("Chests within this many meters of the payment chest get sorted.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), Array.Empty<object>()));
			WalkToChests = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "WalkToChests", true, "If true, a dvergr walks to each chest. If false, sorting happens instantly.");
			CloseDoorsBehind = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "CloseDoorsBehind", true, "The dvergr closes doors it opened once it has walked through.");
			UnreachableRetryMinutes = ((BaseUnityPlugin)this).Config.Bind<float>("General", "UnreachableRetryMinutes", 3f, new ConfigDescription("After the dvergr fails to walk to a chest, leave it out of sorting for this long before trying again.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1440f), Array.Empty<object>()));
			RescanSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RescanSeconds", 30f, new ConfigDescription("While the dvergr is paid, how often it re-checks the chests after finishing a round.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 600f), Array.Empty<object>()));
			CoinsPerStack = ((BaseUnityPlugin)this).Config.Bind<int>("General", "CoinsPerStack", 1, new ConfigDescription("Coins the dvergr takes from the payment chest per stack delivered to its final chest. Make-room moves and put-backs are free.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			HeldItemScale = ((BaseUnityPlugin)this).Config.Bind<float>("Visuals", "HeldItemScale", 2f, new ConfigDescription("Size of the item the dvergr carries, relative to its normal size (capped at 1.2 m).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 4f), Array.Empty<object>()));
			Recipe = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ChestRecipe", "YggdrasilWood:10,BlackMarble:10,Iron:4", "Build cost of the Dvergr Payment Chest, as Item:Amount pairs (prefab names, or item names like yggdrasilwood). The default needs Mistlands materials, so the chest only shows up in the build menu once you've found them. Requires restart. Use the same value on every client and the server.");
			CraftingStation = ((BaseUnityPlugin)this).Config.Bind<string>("General", "CraftingStation", "$piece_blackforge", "Crafting station required nearby to build the payment chest (prefab name or $piece_ key). Empty = none. Requires restart.");
			ItemBlocklist = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ItemBlocklist", "", "Comma-separated item names (e.g. $item_wood) that are never moved.");
			_harmony = new Harmony("com.chase.dvergrsorter");
			_harmony.PatchAll(typeof(DvergrSorterPlugin).Assembly);
			Log.LogInfo((object)"DvergrSorter 1.2.5 loaded");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class Frames
	{
		internal static Transform Of(Component c)
		{
			if ((Object)(object)c == (Object)null)
			{
				return null;
			}
			Transform root = c.transform.root;
			if (!((Object)(object)root == (Object)(object)c.transform))
			{
				return root;
			}
			return null;
		}

		internal static Vector3 ToLocal(Transform frame, Vector3 world)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			if (!((Object)(object)frame == (Object)null))
			{
				return Quaternion.Inverse(frame.rotation) * (world - frame.position);
			}
			return world;
		}

		internal static Vector3 ToWorld(Transform frame, Vector3 local)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)frame == (Object)null))
			{
				return frame.position + frame.rotation * local;
			}
			return local;
		}
	}
	internal static class ItemGroups
	{
		private class Group
		{
			public string Name;

			public HashSet<ItemType> Types = new HashSet<ItemType>();

			public List<Regex> Patterns = new List<Regex>();
		}

		private const string FileName = "DvergrSorter.groups.txt";

		private const string Fallback = "Other";

		private const string DefaultFile = "# DvergrSorter item groups: the dvergr keeps each group together, one group per chest where possible.\n# One group per line:   Group name = rule, rule, ...\n# The first line with a matching rule wins. A rule is either\n#   type:ItemType   Valheim's item type: OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield,\n#                   Torch, Attach_Atgeir, Helmet, Chest, Legs, Hands, Shoulder, Utility, Trinket, Tool, Ammo,\n#                   AmmoNonEquipable, Trophy, Fish, Consumable, Material, Misc, Customization\n#   anything else   a regular expression matched against the item's internal name without '$item_'\n#                   (e.g. wood, finewood, copperore, deerhide, bonefragments). Case-insensitive.\n# Items matching nothing go to 'Other'. Changes apply on the dvergr's next round.\n\nWeapons & shields = type:OneHandedWeapon, type:TwoHandedWeapon, type:TwoHandedWeaponLeft, type:Bow, type:Attach_Atgeir, type:Shield, type:Torch\nArmor & capes     = type:Helmet, type:Chest, type:Legs, type:Hands, type:Shoulder, type:Utility, type:Trinket\nTools             = type:Tool\nAmmo              = type:Ammo, type:AmmoNonEquipable\nTrophies          = type:Trophy\nFish              = type:Fish\nMeads             = mead\nFood              = type:Consumable\nValuables         = coin, amber, ruby, pearl, gemstone, necklace, jewel\nWood              = wood$, roundlog, elderbark, barkabranch\nOres & metals     = (?<!c)ore$, scrap$, ^(copper|tin|bronze|iron|silver|blackmetal|flametal|gold)$, nails, chain$, ingot\nStone             = stone, marble, grausten, obsidian, flint, crystal, coal, hardrock\nHides & leather   = hide$, pelt$, leather, scale$, chitin, carapace, scraps, straps\nBones             = bone, skull, tooth, antler, horn, carrion\nPlants & seeds    = seed, cone, dandelion, thistle, mushroom, flax, barley, carrot, turnip, onion, root, fern, seaweed, berries, sap\nCooking           = meat, egg, entrails, blubber, flour, honey, dough, blood\nOther materials   = type:Material, type:Misc\n";

		private static List<Group> _groups;

		private static DateTime _loadedStamp;

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

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

		public static string Of(ItemData item)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected I4, but got Unknown
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			EnsureLoaded();
			string text = item.m_shared.m_name ?? "";
			string key = text + "|" + (int)item.m_shared.m_itemType;
			if (_cache.TryGetValue(key, out var value))
			{
				return value;
			}
			string bare = (text.StartsWith("$item_") ? text.Substring(6) : text.TrimStart('$'));
			value = "Other";
			foreach (Group group in _groups)
			{
				if (group.Types.Contains(item.m_shared.m_itemType) || group.Patterns.Any((Regex p) => p.IsMatch(bare)))
				{
					value = group.Name;
					break;
				}
			}
			_cache[key] = value;
			return value;
		}

		public static void EnsureLoaded()
		{
			try
			{
				if (!File.Exists(FilePath))
				{
					File.WriteAllText(FilePath, "# DvergrSorter item groups: the dvergr keeps each group together, one group per chest where possible.\n# One group per line:   Group name = rule, rule, ...\n# The first line with a matching rule wins. A rule is either\n#   type:ItemType   Valheim's item type: OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield,\n#                   Torch, Attach_Atgeir, Helmet, Chest, Legs, Hands, Shoulder, Utility, Trinket, Tool, Ammo,\n#                   AmmoNonEquipable, Trophy, Fish, Consumable, Material, Misc, Customization\n#   anything else   a regular expression matched against the item's internal name without '$item_'\n#                   (e.g. wood, finewood, copperore, deerhide, bonefragments). Case-insensitive.\n# Items matching nothing go to 'Other'. Changes apply on the dvergr's next round.\n\nWeapons & shields = type:OneHandedWeapon, type:TwoHandedWeapon, type:TwoHandedWeaponLeft, type:Bow, type:Attach_Atgeir, type:Shield, type:Torch\nArmor & capes     = type:Helmet, type:Chest, type:Legs, type:Hands, type:Shoulder, type:Utility, type:Trinket\nTools             = type:Tool\nAmmo              = type:Ammo, type:AmmoNonEquipable\nTrophies          = type:Trophy\nFish              = type:Fish\nMeads             = mead\nFood              = type:Consumable\nValuables         = coin, amber, ruby, pearl, gemstone, necklace, jewel\nWood              = wood$, roundlog, elderbark, barkabranch\nOres & metals     = (?<!c)ore$, scrap$, ^(copper|tin|bronze|iron|silver|blackmetal|flametal|gold)$, nails, chain$, ingot\nStone             = stone, marble, grausten, obsidian, flint, crystal, coal, hardrock\nHides & leather   = hide$, pelt$, leather, scale$, chitin, carapace, scraps, straps\nBones             = bone, skull, tooth, antler, horn, carrion\nPlants & seeds    = seed, cone, dandelion, thistle, mushroom, flax, barley, carrot, turnip, onion, root, fern, seaweed, berries, sap\nCooking           = meat, egg, entrails, blubber, flour, honey, dough, blood\nOther materials   = type:Material, type:Misc\n");
				}
				DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(FilePath);
				if (_groups == null || !(lastWriteTimeUtc == _loadedStamp))
				{
					_groups = Parse(File.ReadAllLines(FilePath));
					_loadedStamp = lastWriteTimeUtc;
					_cache.Clear();
					DvergrSorterPlugin.Log.LogInfo((object)string.Format("Loaded {0} item groups from {1}", _groups.Count, "DvergrSorter.groups.txt"));
				}
			}
			catch (Exception ex)
			{
				if (_groups == null)
				{
					_groups = Parse("# DvergrSorter item groups: the dvergr keeps each group together, one group per chest where possible.\n# One group per line:   Group name = rule, rule, ...\n# The first line with a matching rule wins. A rule is either\n#   type:ItemType   Valheim's item type: OneHandedWeapon, TwoHandedWeapon, TwoHandedWeaponLeft, Bow, Shield,\n#                   Torch, Attach_Atgeir, Helmet, Chest, Legs, Hands, Shoulder, Utility, Trinket, Tool, Ammo,\n#                   AmmoNonEquipable, Trophy, Fish, Consumable, Material, Misc, Customization\n#   anything else   a regular expression matched against the item's internal name without '$item_'\n#                   (e.g. wood, finewood, copperore, deerhide, bonefragments). Case-insensitive.\n# Items matching nothing go to 'Other'. Changes apply on the dvergr's next round.\n\nWeapons & shields = type:OneHandedWeapon, type:TwoHandedWeapon, type:TwoHandedWeaponLeft, type:Bow, type:Attach_Atgeir, type:Shield, type:Torch\nArmor & capes     = type:Helmet, type:Chest, type:Legs, type:Hands, type:Shoulder, type:Utility, type:Trinket\nTools             = type:Tool\nAmmo              = type:Ammo, type:AmmoNonEquipable\nTrophies          = type:Trophy\nFish              = type:Fish\nMeads             = mead\nFood              = type:Consumable\nValuables         = coin, amber, ruby, pearl, gemstone, necklace, jewel\nWood              = wood$, roundlog, elderbark, barkabranch\nOres & metals     = (?<!c)ore$, scrap$, ^(copper|tin|bronze|iron|silver|blackmetal|flametal|gold)$, nails, chain$, ingot\nStone             = stone, marble, grausten, obsidian, flint, crystal, coal, hardrock\nHides & leather   = hide$, pelt$, leather, scale$, chitin, carapace, scraps, straps\nBones             = bone, skull, tooth, antler, horn, carrion\nPlants & seeds    = seed, cone, dandelion, thistle, mushroom, flax, barley, carrot, turnip, onion, root, fern, seaweed, berries, sap\nCooking           = meat, egg, entrails, blubber, flour, honey, dough, blood\nOther materials   = type:Material, type:Misc\n".Split('\n'));
				}
				DvergrSorterPlugin.Log.LogError((object)("Couldn't read DvergrSorter.groups.txt, using defaults: " + ex.Message));
			}
		}

		private static List<Group> Parse(IEnumerable<string> lines)
		{
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			List<Group> list = new List<Group>();
			foreach (string line in lines)
			{
				string text = line.Trim();
				if (text.Length == 0 || text.StartsWith("#"))
				{
					continue;
				}
				int num = text.IndexOf('=');
				if (num <= 0)
				{
					continue;
				}
				Group obj = new Group
				{
					Name = text.Substring(0, num).Trim()
				};
				foreach (string item in from r in text.Substring(num + 1).Split(',')
					select r.Trim() into r
					where r.Length > 0
					select r)
				{
					if (item.StartsWith("type:", StringComparison.OrdinalIgnoreCase))
					{
						if (Enum.TryParse<ItemType>(item.Substring(5).Trim(), ignoreCase: true, out ItemType result))
						{
							obj.Types.Add(result);
							continue;
						}
						DvergrSorterPlugin.Log.LogWarning((object)("Unknown item type in '" + item + "' (group " + obj.Name + ")"));
						continue;
					}
					try
					{
						obj.Patterns.Add(new Regex(item, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant));
					}
					catch (ArgumentException)
					{
						DvergrSorterPlugin.Log.LogWarning((object)("Bad pattern '" + item + "' (group " + obj.Name + ")"));
					}
				}
				list.Add(obj);
			}
			return list;
		}
	}
	internal static class Links
	{
		internal const string Key = "dsort_key";

		internal const string ChestKey = "dsort_chestkey";

		internal const string DvergrKey = "dsort_dvergrkey";

		private static readonly FieldRef<ZDOMan, Dictionary<ZDOID, ZDO>> ObjectsById = AccessTools.FieldRefAccess<ZDOMan, Dictionary<ZDOID, ZDO>>("m_objectsByID");

		internal static long NewKey()
		{
			long num;
			do
			{
				num = Utils.GenerateUID();
			}
			while (num == 0L);
			return num;
		}

		internal static ZDO FindZdo(string prefab, long key)
		{
			if (key == 0L || ZDOMan.instance == null)
			{
				return null;
			}
			int stableHashCode = StringExtensionMethods.GetStableHashCode(prefab);
			foreach (ZDO value in ObjectsById.Invoke(ZDOMan.instance).Values)
			{
				if (value.GetPrefab() == stableHashCode && value.GetLong("dsort_key", 0L) == key)
				{
					return value;
				}
			}
			return null;
		}

		internal static bool AnyZdoNear(string prefab, Vector3 p, float radius)
		{
			//IL_003f: 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_0045: 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)
			if (ZDOMan.instance == null)
			{
				return false;
			}
			int stableHashCode = StringExtensionMethods.GetStableHashCode(prefab);
			foreach (ZDO value in ObjectsById.Invoke(ZDOMan.instance).Values)
			{
				if (value.GetPrefab() == stableHashCode)
				{
					Vector3 val = value.GetPosition() - p;
					if (((Vector3)(ref val)).sqrMagnitude <= radius * radius)
					{
						return true;
					}
				}
			}
			return false;
		}
	}
	internal static class Localization
	{
		[HarmonyPatch(typeof(Localization), "SetupLanguage")]
		private static class SetupLanguagePatch
		{
			private static void Postfix(Localization __instance)
			{
				foreach (KeyValuePair<string, string> item in English)
				{
					AddWord(__instance, item.Key.TrimStart('$'), item.Value);
				}
			}
		}

		public const string BellName = "$piece_dvergrsortingbell";

		public const string BellDesc = "$piece_dvergrsortingbell_desc";

		public const string DvergrName = "$npc_dvergrsorter";

		public const string Start = "$dsort_start";

		public const string Stop = "$dsort_stop";

		public const string Started = "$dsort_started";

		public const string Stopped = "$dsort_stopped";

		public const string Working = "$dsort_working";

		public const string Watching = "$dsort_watching";

		public const string Take = "$dsort_take";

		public const string ChestName = "$piece_dvergrpaymentchest";

		public const string ChestDesc = "$piece_dvergrpaymentchest_desc";

		public const string BellReplaced = "$dsort_bell_replaced";

		private static readonly Dictionary<string, string> English = new Dictionary<string, string>
		{
			{ "$piece_dvergrsortingbell", "Dvergr Sorting Bell" },
			{ "$piece_dvergrsortingbell_desc", "Ring to summon a dvergr who tidies the chests nearby." },
			{ "$npc_dvergrsorter", "Dvergr Tidier" },
			{ "$dsort_start", "Ring: start sorting" },
			{ "$dsort_stop", "Ring: stop sorting" },
			{ "$dsort_started", "The dvergr gets to work" },
			{ "$dsort_stopped", "The dvergr will rest after this trip" },
			{ "$dsort_working", "<color=orange>Sorting...</color>" },
			{ "$dsort_watching", "<color=orange>Keeping watch over the chests</color>" },
			{ "$dsort_take", "Take" },
			{ "$piece_dvergrpaymentchest", "Dvergr Payment Chest" },
			{ "$piece_dvergrpaymentchest_desc", "Feed it coins to hire a dvergr who keeps the chests nearby sorted. Costs 1 coin per stack sorted." },
			{ "$dsort_bell_replaced", "<color=orange>Replaced by the Dvergr Payment Chest. Break this with the hammer.</color>" }
		};

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

		public static string L(string text)
		{
			return Localization.instance.Localize(text);
		}
	}
	internal static class Patches
	{
		[HarmonyPatch(typeof(MonsterAI), "UpdateAI")]
		private static class MonsterAI_UpdateAI
		{
			private static float _lastErrorLog = -999f;

			private static bool Prefix(MonsterAI __instance, float dt, ref bool __result)
			{
				if (!SorterBrain.AIs.Contains(__instance))
				{
					return true;
				}
				__result = false;
				try
				{
					if (!Object.op_Implicit((Object)(object)__instance))
					{
						return false;
					}
					if (!BaseAI_UpdateAI_Original.Call((BaseAI)(object)__instance, dt))
					{
						return false;
					}
					__result = ((Component)__instance).GetComponent<SorterBrain>().Tick(dt);
				}
				catch (Exception arg)
				{
					if (Time.time - _lastErrorLog > 10f)
					{
						_lastErrorLog = Time.time;
						DvergrSorterPlugin.Log.LogError((object)$"Sorter AI tick failed (shown at most every 10 s): {arg}");
					}
				}
				return false;
			}
		}

		[HarmonyPatch]
		internal static class BaseAI_UpdateAI_Original
		{
			[HarmonyReversePatch(/*Could not decode attribute arguments.*/)]
			[HarmonyPatch(typeof(BaseAI), "UpdateAI")]
			internal static bool Call(BaseAI instance, float dt)
			{
				throw new NotImplementedException("stub");
			}
		}

		[HarmonyPatch(typeof(Character), "Damage")]
		private static class Character_Damage
		{
			private static bool Prefix(Character __instance)
			{
				return !SorterBrain.Characters.Contains(__instance);
			}
		}

		[HarmonyPatch(typeof(Character), "GetHoverText")]
		private static class Character_GetHoverText
		{
			private static void Postfix(Character __instance, ref string __result)
			{
				if (SorterBrain.Characters.Contains(__instance))
				{
					SorterBrain component = ((Component)__instance).GetComponent<SorterBrain>();
					if (!((Object)(object)component == (Object)null) && component.IsCarryingVisibly)
					{
						string text = Localization.L("[<color=yellow><b>$KEY_Use</b></color>] $dsort_take " + component.CarryLabel);
						__result = (string.IsNullOrEmpty(__result) ? text : (__result + "\n" + text));
					}
				}
			}
		}

		[HarmonyPatch(typeof(BaseAI), "IsEnemy", new Type[]
		{
			typeof(Character),
			typeof(Character)
		})]
		private static class BaseAI_IsEnemy
		{
			private static void Postfix(Character a, Character b, ref bool __result)
			{
				if (__result && (SorterBrain.Characters.Contains(a) || SorterBrain.Characters.Contains(b)))
				{
					__result = false;
				}
			}
		}
	}
	internal static class Prefabs
	{
		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		private static class ZNetSceneAwake
		{
			[HarmonyPriority(0)]
			private static void Postfix(ZNetScene __instance)
			{
				try
				{
					EnsurePrefabs(__instance);
					Register(__instance, _bell);
					Register(__instance, _dvergr);
					Register(__instance, _chest);
					if ((Object)(object)ObjectDB.instance != (Object)null)
					{
						AddToHammer(ObjectDB.instance, __instance);
					}
				}
				catch (Exception arg)
				{
					DvergrSorterPlugin.Log.LogError((object)$"Prefab registration failed: {arg}");
				}
			}
		}

		[HarmonyPatch(typeof(ObjectDB), "Awake")]
		private static class ObjectDBAwake
		{
			[HarmonyPriority(0)]
			private static void Postfix(ObjectDB __instance)
			{
				try
				{
					if ((Object)(object)ZNetScene.instance != (Object)null)
					{
						AddToHammer(__instance, ZNetScene.instance);
					}
				}
				catch (Exception arg)
				{
					DvergrSorterPlugin.Log.LogError((object)$"Hammer registration failed: {arg}");
				}
			}
		}

		public const string BellPrefab = "DvergrSortingBell";

		public const string DvergrPrefab = "DvergrSorter_Dvergr";

		public const string ChestPrefab = "DvergrPaymentChest";

		private const string ChestLookKey = "$piece_dvergrchest";

		private const string ChestFallbackPrefab = "piece_chest_wood";

		private static readonly string[] BellLookKeys = new string[3] { "$piece_dvergr_lantern_pole", "$piece_dvergr_lantern", "$piece_hoodedlantern" };

		private static readonly string[] BellFallbackPrefabs = new string[2] { "piece_brazierceiling01", "sign" };

		private const string DvergrSource = "Dverger";

		private const string WorkbenchPrefab = "piece_workbench";

		private const string HammerPrefab = "Hammer";

		private static readonly FieldRef<ZNetScene, Dictionary<int, GameObject>> NamedPrefabs = AccessTools.FieldRefAccess<ZNetScene, Dictionary<int, GameObject>>("m_namedPrefabs");

		private static readonly FieldRef<ObjectDB, List<Piece>> BuildPiecesCache = AccessTools.FieldRefAccess<ObjectDB, List<Piece>>("m_buildPieces");

		private static GameObject _holder;

		private static GameObject _bell;

		private static GameObject _dvergr;

		private static GameObject _chest;

		private static void Register(ZNetScene scene, GameObject prefab)
		{
			if (!((Object)(object)prefab == (Object)null))
			{
				if (!scene.m_prefabs.Contains(prefab))
				{
					scene.m_prefabs.Add(prefab);
				}
				NamedPrefabs.Invoke(scene)[StringExtensionMethods.GetStableHashCode(((Object)prefab).name)] = prefab;
			}
		}

		private static void EnsurePrefabs(ZNetScene scene)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			if ((Object)(object)_holder == (Object)null)
			{
				_holder = new GameObject("DvergrSorter_Prefabs");
				_holder.SetActive(false);
				Object.DontDestroyOnLoad((Object)(object)_holder);
			}
			if ((Object)(object)_bell == (Object)null)
			{
				_bell = CreateBell(scene);
			}
			if ((Object)(object)_dvergr == (Object)null)
			{
				_dvergr = CreateDvergr(scene);
			}
			if ((Object)(object)_chest == (Object)null)
			{
				_chest = CreatePaymentChest(scene);
			}
		}

		private static GameObject Clone(GameObject source, string name)
		{
			GameObject obj = Object.Instantiate<GameObject>(source, _holder.transform, false);
			((Object)obj).name = name;
			return obj;
		}

		private static GameObject CreateBell(ZNetScene scene)
		{
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			List<Piece> source = (from p in scene.m_prefabs
				where (Object)(object)p != (Object)null
				select p.GetComponent<Piece>() into p
				where (Object)(object)p != (Object)null
				select p).ToList();
			GameObject val = null;
			string[] bellLookKeys = BellLookKeys;
			foreach (string key in bellLookKeys)
			{
				Piece? obj = ((IEnumerable<Piece>)source).FirstOrDefault((Func<Piece, bool>)((Piece p) => p.m_name == key));
				val = ((obj != null) ? ((Component)obj).gameObject : null);
				if ((Object)(object)val != (Object)null)
				{
					break;
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				val = ((IEnumerable<string>)BellFallbackPrefabs).Select((Func<string, GameObject>)scene.GetPrefab).FirstOrDefault((Func<GameObject, bool>)((GameObject p) => (Object)(object)p != (Object)null));
			}
			if ((Object)(object)val == (Object)null)
			{
				DvergrSorterPlugin.Log.LogError((object)"No prefab found to build the bell from");
				return null;
			}
			DvergrSorterPlugin.Log.LogInfo((object)("Bell look cloned from '" + ((Object)val).name + "'"));
			GameObject val2 = Clone(val, "DvergrSortingBell");
			MonoBehaviour[] componentsInChildren = val2.GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val3 in componentsInChildren)
			{
				if (val3 is Interactable || val3 is Hoverable)
				{
					Object.DestroyImmediate((Object)(object)val3);
				}
			}
			val2.AddComponent<SortingBell>().IsLegacyBell = true;
			Piece component = val2.GetComponent<Piece>();
			component.m_name = "$piece_dvergrsortingbell";
			component.m_description = "$piece_dvergrsortingbell_desc";
			component.m_category = (PieceCategory)0;
			component.m_usage = (UsageTagFlags)1025;
			return val2;
		}

		private static GameObject CreatePaymentChest(ZNetScene scene)
		{
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			GameObject prefab = scene.GetPrefab("piece_chest_wood");
			GameObject val = ((IEnumerable<GameObject>)scene.m_prefabs).FirstOrDefault((Func<GameObject, bool>)((GameObject p) => (Object)(object)p != (Object)null && p.GetComponent<Container>()?.m_name == "$piece_dvergrchest")) ?? prefab;
			if ((Object)(object)val == (Object)null)
			{
				DvergrSorterPlugin.Log.LogError((object)"No prefab found to build the payment chest from");
				return null;
			}
			DvergrSorterPlugin.Log.LogInfo((object)("Payment chest look cloned from '" + ((Object)val).name + "'"));
			GameObject val2 = Clone(val, "DvergrPaymentChest");
			MonoBehaviour[] componentsInChildren = val2.GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val3 in componentsInChildren)
			{
				string name = ((object)val3).GetType().Name;
				switch (name)
				{
				default:
					if (!(name == "Pickable") && !(val3 is Container) && !(val3 is Interactable) && !(val3 is Hoverable))
					{
						continue;
					}
					break;
				case "RandomSpawn":
				case "Destructible":
				case "DropOnDestroyed":
				case "LootSpawner":
					break;
				}
				Object.DestroyImmediate((Object)(object)val3);
			}
			Piece val4 = val2.GetComponent<Piece>() ?? val2.AddComponent<Piece>();
			Piece val5 = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Piece>() : null);
			val4.m_name = "$piece_dvergrpaymentchest";
			val4.m_description = "$piece_dvergrpaymentchest_desc";
			val4.m_category = (PieceCategory)0;
			val4.m_usage = (UsageTagFlags)1025;
			val4.m_canBeRemoved = true;
			if ((Object)(object)val4.m_icon == (Object)null && (Object)(object)val5 != (Object)null)
			{
				val4.m_icon = val5.m_icon;
			}
			if ((Object)(object)val5 != (Object)null)
			{
				val4.m_placeEffect = val5.m_placeEffect;
				val4.m_groundPiece = val5.m_groundPiece;
				val4.m_groundOnly = val5.m_groundOnly;
			}
			val2.AddComponent<SortingBell>();
			return val2;
		}

		private static GameObject CreateDvergr(ZNetScene scene)
		{
			GameObject prefab = scene.GetPrefab("Dverger");
			if ((Object)(object)prefab == (Object)null)
			{
				DvergrSorterPlugin.Log.LogError((object)"Vanilla prefab 'Dverger' not found");
				return null;
			}
			GameObject val = Clone(prefab, "DvergrSorter_Dvergr");
			val.GetComponent<Character>().m_name = "$npc_dvergrsorter";
			MonsterAI component = val.GetComponent<MonsterAI>();
			if ((Object)(object)component == (Object)null)
			{
				DvergrSorterPlugin.Log.LogError((object)("'Dverger' has no MonsterAI (" + string.Join(",", from c in val.GetComponents<Component>()
					select ((object)c).GetType().Name) + ")"));
				return null;
			}
			component.m_sleeping = false;
			((BaseAI)component).m_aggravatable = false;
			component.m_attackPlayerObjects = false;
			component.m_alertRange = 0f;
			Type[] array = new Type[2]
			{
				typeof(CharacterDrop),
				typeof(NpcTalk)
			};
			foreach (Type type in array)
			{
				Component[] componentsInChildren = val.GetComponentsInChildren(type, true);
				for (int num2 = 0; num2 < componentsInChildren.Length; num2++)
				{
					Object.DestroyImmediate((Object)(object)componentsInChildren[num2]);
				}
			}
			val.AddComponent<SorterBrain>();
			DvergrSorterPlugin.Log.LogInfo((object)"Created DvergrSorter_Dvergr");
			return val;
		}

		private static void AddToHammer(ObjectDB db, ZNetScene scene)
		{
			if ((Object)(object)_chest == (Object)null)
			{
				return;
			}
			GameObject itemPrefab = db.GetItemPrefab("Hammer");
			PieceTable val = ((itemPrefab != null) ? itemPrefab.GetComponent<ItemDrop>() : null)?.m_itemData?.m_shared?.m_buildPieces;
			if ((Object)(object)val == (Object)null)
			{
				DvergrSorterPlugin.Log.LogWarning((object)"Hammer piece table not found (yet)");
				return;
			}
			Piece component = _chest.GetComponent<Piece>();
			component.m_craftingStation = FindStation(scene, DvergrSorterPlugin.CraftingStation.Value);
			component.m_resources = ParseRecipe(db).ToArray();
			if (!val.m_pieces.Contains(_chest))
			{
				val.m_pieces.Add(_chest);
				BuildPiecesCache.Invoke(db)?.Clear();
				DvergrSorterPlugin.Log.LogInfo((object)string.Format("Added {0} to the hammer ({1} requirements)", "DvergrPaymentChest", component.m_resources.Length));
			}
		}

		private static CraftingStation FindStation(ZNetScene scene, string name)
		{
			name = (name ?? "").Trim();
			if (name.Length == 0)
			{
				return null;
			}
			GameObject prefab = scene.GetPrefab(name);
			CraftingStation val = ((prefab != null) ? prefab.GetComponent<CraftingStation>() : null) ?? (from p in scene.m_prefabs
				where (Object)(object)p != (Object)null
				select p.GetComponent<CraftingStation>()).FirstOrDefault((Func<CraftingStation, bool>)((CraftingStation s) => (Object)(object)s != (Object)null && string.Equals(s.m_name, name, StringComparison.OrdinalIgnoreCase)));
			if ((Object)(object)val == (Object)null)
			{
				DvergrSorterPlugin.Log.LogWarning((object)("Crafting station '" + name + "' not found; the payment chest needs no station"));
			}
			else
			{
				DvergrSorterPlugin.Log.LogInfo((object)("Payment chest requires " + Localization.L(val.m_name) + " (" + ((Object)val).name + ")"));
			}
			return val;
		}

		private static IEnumerable<Requirement> ParseRecipe(ObjectDB db)
		{
			string[] array = DvergrSorterPlugin.Recipe.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string obj in array)
			{
				string[] array2 = obj.Split(':');
				if (array2.Length == 2 && int.TryParse(array2[1].Trim(), out var result))
				{
					string name = array2[0].Trim();
					GameObject itemPrefab = db.GetItemPrefab(name);
					ItemDrop val = ((itemPrefab != null) ? itemPrefab.GetComponent<ItemDrop>() : null) ?? (from val2 in db.m_items
						where (Object)(object)val2 != (Object)null
						select val2.GetComponent<ItemDrop>()).FirstOrDefault((Func<ItemDrop, bool>)((ItemDrop val2) => (Object)(object)val2 != (Object)null && string.Equals(val2.m_itemData.m_shared.m_name, "$item_" + name, StringComparison.OrdinalIgnoreCase)));
					if ((Object)(object)val == (Object)null)
					{
						DvergrSorterPlugin.Log.LogWarning((object)("Recipe item '" + name + "' not found"));
						continue;
					}
					yield return new Requirement
					{
						m_resItem = val,
						m_amount = result,
						m_recover = true
					};
				}
			}
		}
	}
	internal class ReachCache
	{
		private struct Entry
		{
			public bool Reachable;

			public long RetryAtTicks;
		}

		private readonly Dictionary<string, Entry> _entries = new Dictionary<string, Entry>();

		private static long Now
		{
			get
			{
				if (!((Object)(object)ZNet.instance != (Object)null))
				{
					return DateTime.UtcNow.Ticks;
				}
				return ZNet.instance.GetTime().Ticks;
			}
		}

		public static string Id(Container c)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: 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_004e: 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)
			Vector3 val = ((Component)c).transform.position;
			Transform val2 = Frames.Of((Component)(object)c);
			if ((Object)(object)val2 != (Object)null)
			{
				val = Frames.ToLocal(val2, val);
			}
			return (((Object)(object)val2 != (Object)null) ? "on:" : "") + string.Format(CultureInfo.InvariantCulture, "{0:F1},{1:F1},{2:F1}", val.x, val.y, val.z);
		}

		public bool IsReachable(Container c)
		{
			if (_entries.TryGetValue(Id(c), out var value))
			{
				return value.Reachable;
			}
			return false;
		}

		public bool IsBlocked(Container c)
		{
			if (_entries.TryGetValue(Id(c), out var value) && !value.Reachable)
			{
				return Now < value.RetryAtTicks;
			}
			return false;
		}

		public void MarkReachable(Container c)
		{
			_entries[Id(c)] = new Entry
			{
				Reachable = true
			};
		}

		public void MarkUnreachable(Container c, float retryMinutes)
		{
			_entries[Id(c)] = new Entry
			{
				Reachable = false,
				RetryAtTicks = Now + TimeSpan.FromMinutes(retryMinutes).Ticks
			};
		}

		public void Clear()
		{
			_entries.Clear();
		}

		public IEnumerable<string> Describe()
		{
			long now = Now;
			foreach (KeyValuePair<string, Entry> item in _entries.OrderBy((KeyValuePair<string, Entry> k) => k.Key))
			{
				yield return item.Value.Reachable ? ("  reachable    " + item.Key) : $"  unreachable  {item.Key}  (retry in {Math.Max(0L, (item.Value.RetryAtTicks - now) / 10000000)}s)";
			}
		}

		public string Serialize()
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<string, Entry> entry in _entries)
			{
				stringBuilder.Append(entry.Key).Append('|').Append(entry.Value.Reachable ? 'R' : 'U')
					.Append('|')
					.Append(entry.Value.RetryAtTicks)
					.Append(';');
			}
			return stringBuilder.ToString();
		}

		public static ReachCache Deserialize(string s)
		{
			ReachCache reachCache = new ReachCache();
			if (string.IsNullOrEmpty(s))
			{
				return reachCache;
			}
			string[] array = s.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split('|');
				if (array2.Length == 3 && long.TryParse(array2[2], out var result))
				{
					reachCache._entries[array2[0]] = new Entry
					{
						Reachable = (array2[1] == "R"),
						RetryAtTicks = result
					};
				}
			}
			return reachCache;
		}
	}
	internal static class SortCommand
	{
		[HarmonyPatch(typeof(Terminal), "InitTerminal")]
		private static class InitTerminalPatch
		{
			[Serializable]
			[CompilerGenerated]
			private sealed class <>c
			{
				public static readonly <>c <>9 = new <>c();

				public static ConsoleEvent <>9__0_0;

				internal void <Postfix>b__0_0(ConsoleEventArgs args)
				{
					Run((args.Args.Length > 1) ? args.Args[1] : null, args.Context, (args.Args.Length > 2) ? args.Args[2] : null);
				}
			}

			private static void Postfix()
			{
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				//IL_0037: Expected O, but got Unknown
				if (_registered)
				{
					return;
				}
				_registered = true;
				object obj = <>c.<>9__0_0;
				if (obj == null)
				{
					ConsoleEvent val = delegate(ConsoleEventArgs args)
					{
						Run((args.Args.Length > 1) ? args.Args[1] : null, args.Context, (args.Args.Length > 2) ? args.Args[2] : null);
					};
					<>c.<>9__0_0 = val;
					obj = (object)val;
				}
				new ConsoleCommand("dsort", "[plan|homes|rehome|cache|forget|come|grid|cleanup] - sort chests around you; 'grid' saves a picture of the dvergr's walk map to BepInEx\\dsort_grid.png; 'plan' only prints moves; 'homes'/'rehome' show or reset which chest holds which group; 'cache'/'forget' inspect or reset the reachable-chest list; 'come' makes the dvergr walk to you; 'cleanup' removes leftover dvergr ('cleanup all' keeps only the nearest bell's)", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			}
		}

		private static bool _registered;

		private static void Run(string arg, Terminal context, string args2 = null)
		{
			//IL_065e: Unknown result type (might be due to invalid IL or missing references)
			//IL_05be: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			//IL_026f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_0312: Unknown result type (might be due to invalid IL or missing references)
			Player player = Player.m_localPlayer;
			if ((Object)(object)player == (Object)null)
			{
				Print(context, "No local player");
				return;
			}
			switch (arg)
			{
			case "cleanup":
			{
				bool flag = args2 == "all";
				List<SorterBrain> list = (from sorterBrain3 in Object.FindObjectsByType<SorterBrain>((FindObjectsSortMode)0)
					where sorterBrain3.IsValid
					select sorterBrain3).ToList();
				List<SortingBell> source = SortingBell.All.Where((SortingBell sortingBell2) => sortingBell2.Key != 0).ToList();
				SortingBell nearestBell = source.OrderBy(delegate(SortingBell sortingBell2)
				{
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					//IL_0016: Unknown result type (might be due to invalid IL or missing references)
					//IL_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)
					Vector3 val = ((Component)sortingBell2).transform.position - ((Component)player).transform.position;
					return ((Vector3)(ref val)).sqrMagnitude;
				}).FirstOrDefault();
				HashSet<SorterBrain> hashSet = new HashSet<SorterBrain>();
				foreach (IGrouping<long, SorterBrain> group in from sorterBrain3 in list
					where sorterBrain3.ChestKey != 0L && sorterBrain3.LeftoverReason() == null
					group sorterBrain3 by sorterBrain3.ChestKey)
				{
					SortingBell bell = source.FirstOrDefault((SortingBell x) => x.Key == group.Key);
					SorterBrain sorterBrain = (((Object)(object)bell != (Object)null) ? group.FirstOrDefault((SorterBrain sorterBrain3) => sorterBrain3.OwnKey == bell.DvergrKey) : null);
					hashSet.Add(sorterBrain ?? group.First());
				}
				if (flag && (Object)(object)nearestBell != (Object)null)
				{
					hashSet.RemoveWhere((SorterBrain sorterBrain3) => sorterBrain3.ChestKey != nearestBell.Key);
				}
				int num = 0;
				foreach (SorterBrain b in list)
				{
					SortingBell sortingBell = source.FirstOrDefault((SortingBell x) => x.Key == b.ChestKey);
					string text = (((Object)(object)sortingBell != (Object)null) ? $"bell at {((Component)sortingBell).transform.position:F0} ({Vector3.Distance(((Component)sortingBell).transform.position, ((Component)player).transform.position):F0} m from you)" : "bell not loaded or gone");
					if (hashSet.Contains(b))
					{
						Print(context, $"Keeping dvergr at {((Component)b).transform.position:F0}: current worker of {text}");
						continue;
					}
					string arg2 = b.LeftoverReason() ?? ((b.ChestKey == 0L) ? "not bound to a chest yet" : (flag ? "not the nearest bell's dvergr" : "duplicate for the same bell"));
					Print(context, $"Removing dvergr at {((Component)b).transform.position:F0}: {arg2} ({text})");
					b.Dismiss();
					num++;
				}
				Print(context, $"Removed {num} dvergr, kept {hashSet.Count}. " + (flag ? "" : "Tip: a dvergr whose bell still exists stays; break that bell or use 'dsort cleanup all'."));
				return;
			}
			case "cache":
			case "forget":
			case "come":
			case "homes":
			case "rehome":
			case "grid":
			{
				SorterBrain sorterBrain2 = Object.FindObjectsByType<SorterBrain>((FindObjectsSortMode)0).OrderBy(delegate(SorterBrain sorterBrain3)
				{
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					//IL_0016: Unknown result type (might be due to invalid IL or missing references)
					//IL_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)
					Vector3 val = ((Component)sorterBrain3).transform.position - ((Component)player).transform.position;
					return ((Vector3)(ref val)).sqrMagnitude;
				}).FirstOrDefault();
				if ((Object)(object)sorterBrain2 == (Object)null)
				{
					Print(context, "No dvergr nearby");
					return;
				}
				switch (arg)
				{
				case "grid":
				{
					string file = Path.Combine(Paths.BepInExRootPath, "dsort_grid.png");
					sorterBrain2.DebugGrid(file, delegate(string msg)
					{
						Print(context, msg);
					});
					break;
				}
				case "rehome":
					sorterBrain2.ForgetHomes();
					Print(context, "Dvergr forgot which chest holds what; it will reassign next round");
					break;
				case "homes":
				{
					Dictionary<string, string> dictionary = sorterBrain2.LoadHomes();
					foreach (IGrouping<string, KeyValuePair<string, string>> item in from kv in dictionary
						group kv by kv.Value into g
						orderby g.Key
						select g)
					{
						Print(context, string.Format("  {0}: {1} chest(s) at {2}", item.Key, item.Count(), string.Join("  ", item.Select((KeyValuePair<string, string> kv) => kv.Key))));
					}
					Print(context, $"{dictionary.Count} chests assigned");
					break;
				}
				case "forget":
					sorterBrain2.ForgetCache();
					Print(context, "Dvergr forgot which chests it can reach");
					break;
				case "come":
					sorterBrain2.Come(((Component)player).transform.position);
					Print(context, "Dvergr is walking to you (see BepInEx log for path details)");
					break;
				default:
				{
					List<string> list2 = sorterBrain2.Cache.Describe().ToList();
					foreach (string item2 in list2)
					{
						Print(context, item2);
					}
					Print(context, $"{list2.Count} chests in the dvergr's list");
					break;
				}
				}
				return;
			}
			}
			bool num2 = arg == "plan";
			List<Container> list3 = ChestSorter.FindContainers(((Component)player).transform.position, DvergrSorterPlugin.SortRadius.Value);
			List<string> list4 = (num2 ? new List<string>() : null);
			var (num3, num4) = ChestSorter.SortInstant(list3, list4, (from sorterBrain3 in Object.FindObjectsByType<SorterBrain>((FindObjectsSortMode)0)
				where sorterBrain3.IsValid
				select sorterBrain3).OrderBy(delegate(SorterBrain sorterBrain3)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_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)
				Vector3 val = ((Component)sorterBrain3).transform.position - ((Component)player).transform.position;
				return ((Vector3)(ref val)).sqrMagnitude;
			}).FirstOrDefault()?.LoadHomes());
			if (num2)
			{
				foreach (string item3 in list4)
				{
					Print(context, item3);
				}
				Print(context, $"{list3.Count} chests in range; {num4} carries planned");
			}
			else
			{
				Print(context, $"{list3.Count} chests in range; moved {num3} stacks in {num4} carries");
			}
		}

		private static void Print(Terminal context, string s)
		{
			if (context != null)
			{
				context.AddString(s);
			}
			DvergrSorterPlugin.Log.LogInfo((object)s);
		}
	}
	public class SorterBrain : MonoBehaviour, Interactable
	{
		private enum State
		{
			Idle,
			Walking,
			Working,
			Returning,
			Visiting
		}

		internal static readonly HashSet<MonsterAI> AIs = new HashSet<MonsterAI>();

		internal static readonly HashSet<Character> Characters = new HashSet<Character>();

		internal static readonly List<SorterBrain> All = new List<SorterBrain>();

		private const string ZdoHome = "dsort_home";

		private const string ZdoCache = "dsort_cache";

		private const string ZdoCarry = "dsort_carry";

		private const string ZdoCarryPrefab = "dsort_carry_prefab";

		private const string ZdoCarryLabel = "dsort_carry_label";

		private const string ZdoWeapons = "dsort_weapons";

		private const string RpcPluck = "dsort_Pluck";

		private const float MaxHeldSize = 1.2f;

		private const float CarryHeight = 0.75f;

		private const float CarryForward = 0.5f;

		private const float ArriveDistance = 2f;

		private const float WorkSeconds = 1.2f;

		private const float StuckSeconds = 8f;

		private const float ChestTimeout = 40f;

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

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

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

		private static readonly FieldRef<BaseAI, List<Vector3>> PathOf = AccessTools.FieldRefAccess<BaseAI, List<Vector3>>("m_path");

		private static readonly FieldRef<Humanoid, ItemData> RightItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_rightItem");

		private static readonly FieldRef<Humanoid, ItemData> LeftItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_leftItem");

		private ZNetView _nview;

		private MonsterAI _ai;

		private Humanoid _humanoid;

		private VisEquipment _vis;

		private State _state;

		private SortPlan _plan;

		private ReachCache _cache;

		private Container _target;

		private Vector3 _targetPoint;

		private bool _targetLidOpen;

		private float _stateTime;

		private float _diagTimer;

		private float _doorScanTimer;

		private float _orphanTimer;

		private float _idleCarryCheck;

		private float _rescanTimer;

		private bool _quietJob;

		private Vector3 _lastProgressPos;

		private float _lastProgressTime;

		private int _moved;

		private int _skipped;

		private int _plucked;

		private readonly Dictionary<Door, bool> _openedDoors = new Dictionary<Door, bool>();

		private Vector3? _doorAssistTarget;

		private readonly Inventory _carry = new Inventory("dsort_carry", (Sprite)null, 8, 4);

		private readonly Dictionary<ChestSorter.Key, Container> _origins = new Dictionary<ChestSorter.Key, Container>();

		private readonly HashSet<Container> _unreachableThisJob = new HashSet<Container>();

		private readonly HashSet<string> _noRoom = new HashSet<string>();

		private readonly HashSet<string> _givenUp = new HashSet<string>();

		private readonly HashSet<Container> _fruitless = new HashSet<Container>();

		private int _visits;

		private const int MaxVisitsPerJob = 300;

		private string _visualPrefab = "";

		private GameObject _visual;

		private SortingBell _homeBell;

		private Vector3 _homeOnBell;

		private int _owed;

		private Container _retriedFromHome;

		private bool _saidBroke;

		private const string ZdoHomes = "dsort_homes";

		private static readonly FieldRef<VisEquipment, GameObject> RightHandModel = AccessTools.FieldRefAccess<VisEquipment, GameObject>("m_rightItemInstance");

		private static readonly FieldRef<VisEquipment, GameObject> LeftHandModel = AccessTools.FieldRefAccess<VisEquipment, GameObject>("m_leftItemInstance");

		private bool _weaponsHidden;

		private readonly List<Renderer> _hiddenRenderers = new List<Renderer>();

		private static bool _loggedHidden;

		private static int _blockMask;

		private float _lineCheckTimer;

		private bool _lineClear;

		private bool _loggedMode;

		private Vector3 _direct;

		private WalkGrid _grid;

		private WalkGridBuilder _builder;

		private List<Vector3> _path;

		private WalkGrid _pathGrid;

		private Task<List<Vector3>> _pathTask;

		private int _pathIdx;

		private Container _pathTarget;

		private Vector3 _pathPoint;

		private bool _pathPlanned;

		private float _skipTimer;

		private bool _replanned;

		private Action<WalkGrid> _onGridReady;

		private Vector3 _stepCheckPos;

		private float _stepCheckTime;

		private float _lastJumpTime;

		private float _orphanFor;

		private const float OrphanGraceSeconds = 15f;

		public bool Busy => _state != State.Idle;

		private Vector3 Home
		{
			get
			{
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_0039: Unknown result type (might be due to invalid IL or missing references)
				//IL_0072: Unknown result type (might be due to invalid IL or missing references)
				//IL_0077: Unknown result type (might be due to invalid IL or missing references)
				//IL_005c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0061: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				SortingBell sortingBell = FindBell();
				if ((Object)(object)sortingBell == (Object)null || (Object)(object)Frames.Of((Component)(object)sortingBell) == (Object)null)
				{
					return _nview.GetZDO().GetVec3("dsort_home", ((Component)this).transform.position);
				}
				if ((Object)(object)_homeBell != (Object)(object)sortingBell)
				{
					_homeBell = sortingBell;
					_homeOnBell = ((Component)sortingBell).transform.InverseTransformPoint(sortingBell.HomePosition());
				}
				return ((Component)sortingBell).transform.TransformPoint(_homeOnBell);
			}
		}

		private Transform Frame => Frames.Of((Component)(object)FindBell());

		internal bool IsValid
		{
			get
			{
				if ((Object)(object)_nview != (Object)null)
				{
					return _nview.IsValid();
				}
				return false;
			}
		}

		internal ZDOID Id => _nview.GetZDO().m_uid;

		internal long OwnKey => _nview.GetZDO().GetLong("dsort_key", 0L);

		internal long ChestKey => _nview.GetZDO().GetLong("dsort_chestkey", 0L);

		internal ReachCache Cache => _cache ?? (_cache = ReachCache.Deserialize(_nview.GetZDO().GetString("dsort_cache", "")));

		internal bool IsCarryingVisibly
		{
			get
			{
				if ((Object)(object)_nview != (Object)null && _nview.IsValid())
				{
					return _nview.GetZDO().GetString("dsort_carry_prefab", "") != "";
				}
				return false;
			}
		}

		internal string CarryLabel => _nview.GetZDO().GetString("dsort_carry_label", "");

		private static int BlockMask
		{
			get
			{
				if (_blockMask == 0)
				{
					return _blockMask = LayerMask.GetMask(new string[6] { "Default", "static_solid", "Default_small", "piece", "terrain", "vehicle" });
				}
				return _blockMask;
			}
		}

		private void Awake()
		{
			_nview = ((Component)this).GetComponent<ZNetView>();
			_ai = ((Component)this).GetComponent<MonsterAI>();
			_humanoid = ((Component)this).GetComponent<Humanoid>();
			_vis = ((Component)this).GetComponent<VisEquipment>();
			if ((Object)(object)_ai != (Object)null)
			{
				AIs.Add(_ai);
			}
			if ((Object)(object)_humanoid != (Object)null)
			{
				Characters.Add((Character)(object)_humanoid);
			}
			All.Add(this);
			if ((Object)(object)_nview != (Object)null && _nview.GetZDO() != null)
			{
				_nview.Register("dsort_Pluck", (Action<long>)RPC_Pluck);
			}
		}

		private void OnDestroy()
		{
			AIs.Remove(_ai);
			Characters.Remove((Character)(object)_humanoid);
			All.Remove(this);
			_builder?.Dispose();
			_builder = null;
			CloseLid();
			if ((Object)(object)_visual != (Object)null)
			{
				Object.Destroy((Object)(object)_visual);
			}
		}

		internal void Bind(SortingBell bell, Vector3 home)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			ZDO zDO = _nview.GetZDO();
			zDO.Set("dsort_key", Links.NewKey());
			zDO.Set("dsort_chestkey", bell.Key);
			zDO.Set("dsort_home", home);
		}

		internal void Adopt(SortingBell bell)
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			if (OwnKey == 0L || ChestKey != bell.Key)
			{
				if (!_nview.IsOwner())
				{
					_nview.ClaimOwnership();
				}
				ZDO zDO = _nview.GetZDO();
				if (OwnKey == 0L)
				{
					zDO.Set("dsort_key", Links.NewKey());
				}
				if (ChestKey != bell.Key)
				{
					zDO.Set("dsort_chestkey", bell.Key);
					DvergrSorterPlugin.Log.LogInfo((object)$"Dvergr at {((Component)this).transform.position} bound to the payment chest at {((Component)bell).transform.position}");
				}
			}
		}

		private Vector3 ToLocal(Vector3 world)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			return Frames.ToLocal(Frame, world);
		}

		private Vector3 ToWorld(Vector3 local)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			return Frames.ToWorld(Frame, local);
		}

		private bool CanKeepWorking()
		{
			SortingBell sortingBell = FindBell();
			if ((Object)(object)sortingBell != (Object)null && !sortingBell.IsLegacyBell)
			{
				return sortingBell.Coins > _owed;
			}
			return false;
		}

		private void Earn(int stacksDelivered)
		{
			_owed += stacksDelivered * DvergrSorterPlugin.CoinsPerStack.Value;
			if (_owed <= 0)
			{
				return;
			}
			SortingBell sortingBell = FindBell();
			if (!((Object)(object)sortingBell == (Object)null))
			{
				int num = sortingBell.Charge(_owed);
				_owed -= num;
				if (num > 0)
				{
					DvergrSorterPlugin.Log.LogInfo((object)$"Took {num} coins ({sortingBell.Coins} left)");
				}
			}
		}

		private SortingBell FindBell()
		{
			long chestKey = ChestKey;
			if (chestKey == 0L)
			{
				return null;
			}
			foreach (SortingBell item in SortingBell.All)
			{
				if (item.Key == chestKey)
				{
					return item;
				}
			}
			return null;
		}

		internal void Dismiss()
		{
			if (!_nview.IsOwner())
			{
				_nview.ClaimOwnership();
			}
			CloseLid();
			if (_carry.NrOfItems() > 0)
			{
				StashCarry(null);
			}
			else
			{
				_plan = null;
				DropStrayCarry();
			}
			_nview.Destroy();
		}

		private void SaveCache()
		{
			if (_nview.IsOwner())
			{
				_nview.GetZDO().Set("dsort_cache", Cache.Serialize());
			}
		}

		internal Dictionary<string, string> LoadHomes()
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			string[] array = _nview.GetZDO().GetString("dsort_homes", "").Split('\n');
			foreach (string text in array)
			{
				int num = text.IndexOf('\t');
				if (num > 0)
				{
					dictionary[text.Substring(0, num)] = text.Substring(num + 1);
				}
			}
			return dictionary;
		}

		private void SaveHomes()
		{
			if (_plan == null || !_nview.IsOwner())
			{
				return;
			}
			Dictionary<string, string> dictionary = LoadHomes();
			foreach (KeyValuePair<Container, string> item in _plan.Owner)
			{
				if (item.Value == "#locked")
				{
					dictionary.Remove(ReachCache.Id(item.Key));
				}
				else
				{
					dictionary[ReachCache.Id(item.Key)] = item.Value;
				}
			}
			_nview.GetZDO().Set("dsort_homes", string.Join("\n", dictionary.Select((KeyValuePair<string, string> kv) => kv.Key + "\t" + kv.Value)));
			_plan.HomesChanged = false;
		}

		internal void ForgetHomes()
		{
			if (!_nview.IsOwner())
			{
				_nview.ClaimOwnership();
			}
			_nview.GetZDO().Set("dsort_homes", "");
		}

		internal void ForgetCache()
		{
			if (!_nview.IsOwner())
			{
				_nview.ClaimOwnership();
			}
			Cache.Clear();
			SaveCache();
		}

		public void StartJob(SortingBell bell, List<Container> inRange, bool quiet = false)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			_quietJob = quiet;
			if (!_nview.IsOwner())
			{
				_nview.ClaimOwnership();
			}
			if ((Object)(object)bell != (Object)null)
			{
				_nview.GetZDO().Set("dsort_home", bell.HomePosition());
			}
			_cache = null;
			_plan = null;
			DropStrayCarry();
			List<Container> list = new List<Container>();
			int num = 0;
			foreach (Container item in inRange)
			{
				if (Cache.IsBlocked(item))
				{
					num++;
				}
				else
				{
					list.Add(item);
				}
			}
			_plan = ChestSorter.Plan(list, LoadHomes());
			SaveHomes();
			_moved = (_skipped = (_plucked = (_visits = 0)));
			_origins.Clear();
			_unreachableThisJob.Clear();
			_noRoom.Clear();
			_givenUp.Clear();
			_fruitless.Clear();
			if (quiet && _plan.Tasks.Count == 0)
			{
				_plan = null;
				return;
			}
			if (_plan.Tasks.Count > 0)
			{
				EnsureGrid(!quiet);
			}
			DvergrSorterPlugin.Log.LogInfo((object)($"Job: {inRange.Count} chests in range, {list.Count} reachable, {num} unreachable; " + $"{_plan.Homes.Count} item types, {_plan.Tasks.Count} carries"));
			foreach (SortTask task in _plan.Tasks)
			{
				DvergrSorterPlugin.Log.LogInfo((object)$"  plan: {task.Key} [{task.Group}]  {ChestSorter.Describe(task.From)} -> {ChestSorter.Describe(task.To)}");
			}
			if (_plan.Tasks.Count == 0)
			{
				_plan = null;
				Say((num > 0) ? $"Everything I can reach is in order. ({num} chests I can't get to.)" : "Everything is already in its place.");
				return;
			}
			if (!quiet)
			{
				Say("I'll tidy up.");
			}
			NextStop();
		}

		public void Come(Vector3 point)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: 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_004e: 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_0073: 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)
			if (Busy)
			{
				Say("I'm busy.");
				return;
			}
			if (!_nview.IsOwner())
			{
				_nview.ClaimOwnership();
			}
			_targetPoint = point;
			Pathfinding instance = Pathfinding.instance;
			bool flag = (Object)(object)instance != (Object)null && instance.HavePath(((Component)this).transform.position, point, ((BaseAI)_ai).m_pathAgentType);
			DvergrSorterPlugin.Log.LogInfo((object)$"Come: from {((Component)this).transform.position} to {point}, quick path check {flag}");
			Say("Coming.");
			_doorAssistTarget = null;
			SetState(State.Visiting);
		}

		private static string NoRoomKey(string group, Container c)
		{
			return group + "@" + ((Object)c).GetInstanceID();
		}

		private string G(ItemData item)
		{
			return ChestSorter.HomeKey(_plan, item);
		}

		private bool Usable(Container c)
		{
			if ((Object)(object)c != (Object)null)
			{
				return !_unreachableThisJob.Contains(c);
			}
			return false;
		}

		private IEnumerable<Container> OpenHomes(string group)
		{
			if (!_plan.Homes.TryGetValue(group, out var value))
			{
				return Enumerable.Empty<Container>();
			}
			return value.Where((Container h) => Usable(h) && !_noRoom.Contains(NoRoomKey(group, h)));
		}

		private bool Actionable(ItemData item, Container where)
		{
			string text = G(item);
			if (!_givenUp.Contains(text))
			{
				return OpenHomes(text).Any((Container h) => (Object)(object)h != (Object)(object)where);
			}
			return false;
		}

		private Container Nearest(IEnumerable<Container> chests)
		{
			return chests.Where(Usable).OrderBy(delegate(Container c)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val = ((Component)c).transform.position - ((Component)this).transform.position;
				return ((Vector3)(ref val)).sqrMagnitude;
			}).FirstOrDefault();
		}

		private void NextStop()
		{
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			Container val = null;
			if (_plan != null && _visits < 300)
			{
				if (_carry.NrOfItems() > 0)
				{
					List<string> list = _carry.GetAllItems().Select(G).Distinct()
						.ToList();
					val = Nearest(list.Where((string g) => !_givenUp.Contains(g)).SelectMany(OpenHomes));
					if ((Object)(object)val == (Object)null)
					{
						foreach (string item in list)
						{
							_givenUp.Add(item);
						}
						val = Nearest(from i in _carry.GetAllItems()
							select (!_origins.TryGetValue(ChestSorter.KeyOf(i), out var value)) ? null : value into o
							where (Object)(object)o != (Object)null && !ChestLock.IsLocked(o) && _carry.GetAllItems().Any((ItemData i) => ChestSorter.HasRoomFor(o, i))
							select o) ?? Nearest(_plan.Chests.Where((Container c) => !_plan.Locked.Contains(c) && _carry.GetAllItems().Any((ItemData i) => ChestSorter.HasRoomFor(c, i))));
						if ((Object)(object)val == (Object)null)
						{
							DropCarryOnGround("No room anywhere for this. I'll leave it here.");
						}
					}
				}
				if ((Object)(object)val == (Object)null && _carry.NrOfItems() == 0)
				{
					val = Nearest(_plan.Chests.Where((Container c) => !_fruitless.Contains(c) && ChestSorter.Foreign(_plan, c).Any((ItemData i) => Actionable(i, c))));
				}
			}
			_target = val;
			_doorAssistTarget = null;
			if ((Object)(object)_target == (Object)null)
			{
				SetState(State.Returning);
				return;
			}
			_targetPoint = ((Component)_target).transform.position;
			SetState(State.Walking);
		}

		internal bool Tick(float dt)
		{
			//IL_0086: 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)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_03be: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0396: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0304: Unknown result type (might be due to invalid IL or missing references)
			//IL_0314: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_024e: Unknown result type (might be due to invalid IL or missing references)
			if (((BaseAI)_ai).IsSleeping())
			{
				Wakeup(_ai);
				DvergrSorterPlugin.Log.LogInfo((object)"Woke the dvergr up");
			}
			TickGridBuilder();
			_stateTime += dt;
			if (CheckOrphan(dt))
			{
				return false;
			}
			if (_state != State.Idle)
			{
				HandleDoors(dt);
			}
			switch (_state)
			{
			case State.Idle:
			{
				Vector3 val = ((Component)this).transform.position - Home;
				if (((Vector3)(ref val)).sqrMagnitude > 4f)
				{
					Walk(dt, Home, 1f, null);
					if (IsStuck())
					{
						TeleportHome("stuck while idle");
					}
				}
				else
				{
					((BaseAI)_ai).StopMoving();
					_lastProgressPos = ToLocal(((Component)this).transform.position);
					_lastProgressTime = Time.time;
				}
				_idleCarryCheck += dt;
				if (_idleCarryCheck > 2f)
				{
					_idleCarryCheck = 0f;
					DropStrayCarry();
				}
				_rescanTimer += dt;
				if (_rescanTimer >= DvergrSorterPlugin.RescanSeconds.Value)
				{
					_rescanTimer = 0f;
					SortingBell sortingBell = FindBell();
					if ((Object)(object)sortingBell != (Object)null && CanKeepWorking())
					{
						_saidBroke = false;
						StartJob(sortingBell, ChestSorter.FindContainers(((Component)sortingBell).transform.position, DvergrSorterPlugin.SortRadius.Value), quiet: true);
					}
					else if ((Object)(object)sortingBell != (Object)null && !_saidBroke)
					{
						_saidBroke = true;
						Say("No coins, no work. I'll be resting.");
					}
				}
				break;
			}
			case State.Walking:
			{
				if ((Object)(object)_target == (Object)null)
				{
					NextStop();
					break;
				}
				bool num = Walk(dt, _targetPoint, 2f, _target) || CloseEnough(_target);
				LogProgress(dt);
				if (num)
				{
					BeginWork();
				}
				else if ((IsStuck() || _stateTime > 40f) && !TryReplan() && !TryDoorAssist(((Component)_target).transform.position))
				{
					GiveUpOnTarget();
				}
				break;
			}
			case State.Visiting:
			{
				bool num2 = Walk(dt, _targetPoint, 1.5f, null);
				LogProgress(dt);
				if (num2)
				{
					((BaseAI)_ai).StopMoving();
					CloseAllDoors();
					Say("Here I am.");
					DvergrSorterPlugin.Log.LogInfo((object)$"Arrived at {((Component)this).transform.position}");
					SetState(State.Idle);
				}
				else if ((IsStuck() || _stateTime > 40f) && !TryDoorAssist(_targetPoint))
				{
					DvergrSorterPlugin.Log.LogInfo((object)$"Couldn't come to {_targetPoint}: stopped at {((Component)this).transform.position}, path corners {PathOf.Invoke((BaseAI)(object)_ai)?.Count ?? (-1)}");
					Say("I can't find a way there.");
					CloseAllDoors();
					SetState(State.Idle);
				}
				break;
			}
			case State.Working:
				((BaseAI)_ai).StopMoving();
				if ((Object)(object)_target != (Object)null)
				{
					LookAt((BaseAI)(object)_ai, ((Component)_target).transform.position);
				}
				if (_stateTime >= 1.2f)
				{
					FinishWork();
				}
				break;
			case State.Returning:
			{
				bool flag = Walk(dt, Home, 1f, null);
				if (flag || IsStuck() || _stateTime > 40f)
				{
					if (!flag)
					{
						TeleportHome("stuck on the way back");
					}
					((BaseAI)_ai).StopMoving();
					CloseAllDoors();
					if (_carry.NrOfItems() > 0)
					{
						StashCarry("I'll put these back for now.");
					}
					string text = $"Done! Moved {_moved} stacks.";
					if (_skipped > 0)
					{
						text += string.Format(" Couldn't get to {0} chest{1}; I'll try again later.", _skipped, (_skipped == 1) ? "" : "s");
					}
					if (_plucked > 0)
					{
						text += " Someone kept taking my loads!";
					}
					if (!_quietJob || _moved > 0 || _plucked > 0)
					{
						Say(text);
					}
					DvergrSorterPlugin.Log.LogInfo((object)text);
					_plan = null;
					_rescanTimer = 0f;
					SetState(State.Idle);
				}
				break;
			}
			}
			return true;
		}

		private void TeleportHome(string why)
		{
			//IL_0001: Unknown result type (might be due t