Decompiled source of AutoStore v0.4.6

plugins/ValheimAutostore.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ValheimAutostore
{
	internal static class AutostoreButton
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__OnClick;
		}

		private const float GapFactor = 1.05f;

		private const float WidthFactor = 0.62f;

		private static Button _button;

		private static TMP_Text _label;

		internal static void Refresh(InventoryGui gui)
		{
			try
			{
				if (!Object.op_Implicit((Object)(object)gui) || !Plugin.Enabled.Value)
				{
					Hide();
					return;
				}
				if (!gui.IsContainerOpen())
				{
					Hide();
					return;
				}
				Container currentContainer = gui.m_currentContainer;
				if (!Object.op_Implicit((Object)(object)currentContainer))
				{
					Hide();
				}
				else if (EnsureCreated(gui))
				{
					((Component)_button).gameObject.SetActive(true);
					if (Object.op_Implicit((Object)(object)_label))
					{
						_label.text = (AutostoreState.IsEnabled(currentContainer) ? $"AStr:{ReferenceSlots.CountFor(currentContainer)}" : "AStr:OFF");
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"AutostoreButton threw: {arg}");
				Hide();
			}
		}

		private static bool EnsureCreated(InventoryGui gui)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected O, but got Unknown
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Expected O, but got Unknown
			if (Object.op_Implicit((Object)(object)_button))
			{
				return true;
			}
			Button stackAllButton = gui.m_stackAllButton;
			if (!Object.op_Implicit((Object)(object)stackAllButton) || !Object.op_Implicit((Object)(object)((Component)stackAllButton).transform.parent))
			{
				return false;
			}
			GameObject val = Object.Instantiate<GameObject>(((Component)stackAllButton).gameObject, ((Component)stackAllButton).transform.parent);
			((Object)val).name = "AutostoreToggle";
			_button = val.GetComponent<Button>();
			if (!Object.op_Implicit((Object)(object)_button))
			{
				Object.Destroy((Object)(object)val);
				return false;
			}
			_label = val.GetComponentInChildren<TMP_Text>();
			PlaceRelativeTo(stackAllButton, val);
			_button.onClick = new ButtonClickedEvent();
			ButtonClickedEvent onClick = _button.onClick;
			object obj = <>O.<0>__OnClick;
			if (obj == null)
			{
				UnityAction val2 = OnClick;
				<>O.<0>__OnClick = val2;
				obj = (object)val2;
			}
			((UnityEvent)onClick).AddListener((UnityAction)obj);
			Plugin.Log.LogInfo((object)"Autostore button added to the chest window.");
			return true;
		}

		private static void PlaceRelativeTo(Button model, GameObject clone)
		{
			//IL_002d: 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_0045: 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)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: 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_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			Transform transform = ((Component)model).transform;
			RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null);
			Transform transform2 = clone.transform;
			RectTransform val2 = (RectTransform)(object)((transform2 is RectTransform) ? transform2 : null);
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null))
			{
				val2.anchorMin = val.anchorMin;
				val2.anchorMax = val.anchorMax;
				val2.pivot = val.pivot;
				((Transform)val2).localScale = ((Transform)val).localScale;
				float num = val.sizeDelta.x * 0.62f;
				val2.sizeDelta = new Vector2(num, val.sizeDelta.y);
				float num2 = (1f - val.pivot.x) * (val.sizeDelta.x - num);
				val2.anchoredPosition = val.anchoredPosition + new Vector2(num2, (0f - val.sizeDelta.y) * 1.05f);
			}
		}

		private static void OnClick()
		{
			try
			{
				InventoryGui instance = InventoryGui.m_instance;
				if (Object.op_Implicit((Object)(object)instance) && instance.IsContainerOpen())
				{
					Container currentContainer = instance.m_currentContainer;
					if (Object.op_Implicit((Object)(object)currentContainer))
					{
						DesignationInput.Designate(currentContainer);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Autostore button click: {arg}");
			}
		}

		private static void Hide()
		{
			if (Object.op_Implicit((Object)(object)_button))
			{
				((Component)_button).gameObject.SetActive(false);
			}
		}
	}
	internal static class AutostoreLoop
	{
		private struct ServedState
		{
			internal Vector3 Position;

			internal float LeftAt;
		}

		private enum ItemSkip
		{
			None,
			Invalid,
			Unique,
			Equipped,
			Hotbar
		}

		private static float _nextScan;

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

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

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

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

		private static readonly string[] Memory = new string[8];

		private static readonly List<ItemData> Candidates = new List<ItemData>();

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

		private static readonly Dictionary<ChestKey, ServedState> Served = new Dictionary<ChestKey, ServedState>();

		private const float NotLeftYet = -1f;

		private static readonly List<ChestKey> Rearmed = new List<ChestKey>();

		private static readonly List<ChestKey> JustLeft = new List<ChestKey>();

		private static long _worldId;

		private static readonly Comparison<Container> ByFullnessDescending = (Container a, Container b) => ItemCount(b).CompareTo(ItemCount(a));

		private static bool _hasMiss;

		private static float _missFlat;

		private static float _missRise;

		internal static void MarkServed(Container container)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)container) && ContainerFinder.TryGetKey(container, out var key))
			{
				Served[key] = new ServedState
				{
					Position = ((Component)container).transform.position,
					LeftAt = -1f
				};
			}
		}

		internal static void Update()
		{
			try
			{
				long num = KnownContainers.CurrentWorldId();
				if (num != _worldId)
				{
					ResetSessionState(num);
				}
				if (Plugin.Enabled.Value)
				{
					Player localPlayer = Player.m_localPlayer;
					if (Object.op_Implicit((Object)(object)localPlayer) && !(Time.time < _nextScan))
					{
						_nextScan = Time.time + Mathf.Max(0.1f, Plugin.CheckInterval.Value);
						Scan(localPlayer);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"AutostoreLoop threw: {arg}");
			}
			finally
			{
				FlushDiagnostics();
			}
		}

		private static void Scan(Player player)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0248: Unknown result type (might be due to invalid IL or missing references)
			if (InventoryGui.IsVisible())
			{
				return;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return;
			}
			Vector3 position = ((Component)player).transform.position;
			float value = Plugin.SearchRadius.Value;
			float value2 = Plugin.VerticalRange.Value;
			float num = Mathf.Max(value + 0.5f, Plugin.RearmDistance.Value);
			float num2 = Mathf.Max(value2 + 0.5f, Plugin.RearmDistance.Value);
			ContainerFinder.FindInRange(position, num, num2, Nearby);
			Nearby.Sort(ByFullnessDescending);
			Moved.Clear();
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			int num6 = 0;
			int num7 = 0;
			ForgetNearestMiss();
			foreach (Container item in Nearby)
			{
				if (!ContainerFinder.TryGetKey(item, out var key))
				{
					continue;
				}
				if (!Proximity.Within(position, ((Component)item).transform.position, value, value2))
				{
					NoteNearestMiss(position, ((Component)item).transform.position, value);
					continue;
				}
				num3++;
				if (!AutostoreState.IsEnabled(item))
				{
					continue;
				}
				num4++;
				ZNetView nview = item.m_nview;
				bool flag = Object.op_Implicit((Object)(object)nview) && nview.IsOwner();
				if (flag && DepositRoutine.SafeLoad(item))
				{
					ReferenceSlots.Remember(item, clearBeyond: false);
				}
				if (!KnownContainers.IsKnown(item))
				{
					continue;
				}
				num5++;
				if (Served.ContainsKey(key))
				{
					num6++;
					continue;
				}
				if (!flag)
				{
					num7++;
					if (HasSomethingFor(item, inventory) && OwnershipHandshake.CanRequest(key))
					{
						OwnershipHandshake.Request(item);
					}
					continue;
				}
				int num8 = TotalMoved();
				DepositInto(item, inventory);
				if (TotalMoved() > num8)
				{
					MarkServed(item);
				}
			}
			UpdateRearming(position, num, num2);
			if (Moved.Count > 0)
			{
				Report(player);
			}
			else if (num3 == 0)
			{
				Diagnose($"no chest within {value} m around you and {value2} m up or down" + NearestMissText() + " " + $"({ContainerFinder.TrackedCount} chest(s) loaded)");
			}
			else if (num4 == 0)
			{
				Diagnose($"{num3} chest(s) in range, none set up - press " + $"{Plugin.DesignateKey.Value} with the chest open");
			}
			else if (num5 == 0)
			{
				Diagnose($"{num4} autostore chest(s) in range, but you have never opened them " + "on this machine - open one once (enrollment rule, SPEC.md §1)");
			}
			else if (num7 > 0 && num7 == num5 - num6)
			{
				Diagnose($"{num7} chest(s) owned by another player - ownership requested, " + "deposit will follow once granted");
			}
			else if (num6 > 0 && num6 == num5)
			{
				Diagnose($"{num6} chest(s) already served - walk {num} m away to start " + $"the {Plugin.RearmDelay.Value} s countdown (it then runs even if you come back)");
			}
		}

		private static int ItemCount(Container container)
		{
			if (!Object.op_Implicit((Object)(object)container))
			{
				return -1;
			}
			Inventory inventory = container.GetInventory();
			if (inventory == null)
			{
				return -1;
			}
			return inventory.GetAllItems()?.Count ?? (-1);
		}

		private static int TotalMoved()
		{
			int num = 0;
			foreach (int value in Moved.Values)
			{
				num += value;
			}
			return num;
		}

		private static void ForgetNearestMiss()
		{
			_hasMiss = false;
		}

		private static void NoteNearestMiss(Vector3 position, Vector3 chest, float radius)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			float num = Proximity.Flat(position, chest);
			if (!(num > radius) && (!_hasMiss || !(num >= _missFlat)))
			{
				_hasMiss = true;
				_missFlat = num;
				_missRise = Proximity.Rise(position, chest);
			}
		}

		private static string NearestMissText()
		{
			if (!_hasMiss)
			{
				return string.Empty;
			}
			return $" - one sits {_missFlat:0.#} m away but {Mathf.Abs(_missRise):0.#} m " + ((_missRise >= 0f) ? "up" : "down") + ", raise VerticalRange to reach it";
		}

		private static void UpdateRearming(Vector3 position, float rearm, float rearmVertical)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			if (Served.Count == 0)
			{
				return;
			}
			float num = Mathf.Max(0f, Plugin.RearmDelay.Value);
			float time = Time.time;
			Rearmed.Clear();
			JustLeft.Clear();
			foreach (KeyValuePair<ChestKey, ServedState> item in Served)
			{
				ServedState value = item.Value;
				if (value.LeftAt == -1f)
				{
					if (!Proximity.Within(position, value.Position, rearm, rearmVertical))
					{
						JustLeft.Add(item.Key);
					}
				}
				else if (time - value.LeftAt >= num)
				{
					Rearmed.Add(item.Key);
				}
			}
			foreach (ChestKey item2 in JustLeft)
			{
				ServedState value2 = Served[item2];
				value2.LeftAt = time;
				Served[item2] = value2;
			}
			foreach (ChestKey item3 in Rearmed)
			{
				Served.Remove(item3);
			}
		}

		private static void ResetSessionState(long world)
		{
			_worldId = world;
			Served.Clear();
			Moved.Clear();
			Pending.Clear();
			LastPass.Clear();
			OwnershipHandshake.Reset();
			OwnershipHandshake.ClearRegisters();
			KnownContainers.ClearPending();
		}

		internal static void CompleteDeposit(Container container)
		{
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!Object.op_Implicit((Object)(object)container) || !Plugin.Enabled.Value)
				{
					return;
				}
				Player localPlayer = Player.m_localPlayer;
				if (!Object.op_Implicit((Object)(object)localPlayer))
				{
					return;
				}
				Inventory inventory = ((Humanoid)localPlayer).GetInventory();
				if (inventory == null)
				{
					return;
				}
				if (InventoryGui.IsVisible())
				{
					Diagnose("completion aborted: an inventory is open");
				}
				else if (!AutostoreState.IsEnabled(container))
				{
					Diagnose("completion aborted: chest no longer set up");
				}
				else if (!KnownContainers.IsKnown(container))
				{
					Diagnose("completion aborted: chest not enrolled");
				}
				else
				{
					if (!ContainerFinder.TryGetKey(container, out var key))
					{
						return;
					}
					if (Served.ContainsKey(key))
					{
						Diagnose("completion aborted: chest locked, walk away to rearm it");
						return;
					}
					float radius = Mathf.Max(Plugin.SearchRadius.Value + 0.5f, Plugin.RearmDistance.Value);
					float vertical = Mathf.Max(Plugin.VerticalRange.Value + 0.5f, Plugin.RearmDistance.Value);
					if (!Proximity.Within(((Component)localPlayer).transform.position, ((Component)container).transform.position, radius, vertical))
					{
						Diagnose("completion aborted: you walked away while waiting");
						return;
					}
					Moved.Clear();
					DepositInto(container, inventory);
					if (TotalMoved() > 0)
					{
						MarkServed(container);
						Report(localPlayer);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"CompleteDeposit threw: {arg}");
			}
			finally
			{
				FlushDiagnostics();
			}
		}

		private static bool HasSomethingFor(Container container, Inventory bag)
		{
			Inventory inventory = container.GetInventory();
			if (inventory == null)
			{
				return false;
			}
			ReferenceSlots.ReadFilters(container, Filters);
			if (Filters.Count == 0)
			{
				return false;
			}
			int refCount = ReferenceSlots.CountFor(container);
			ReferenceSlots.ReadMemory(container, Memory);
			foreach (ItemData allItem in bag.GetAllItems())
			{
				if (WhySkip(allItem) == ItemSkip.None && Filters.Contains(allItem.m_shared.m_name) && BudgetFor(inventory, bag, allItem.m_shared.m_name, out var _) > 0 && DepositRoutine.HasRoomFor(inventory, allItem, refCount, Memory))
				{
					return true;
				}
			}
			return false;
		}

		private static void DepositInto(Container container, Inventory bag)
		{
			Inventory inventory = container.GetInventory();
			if (inventory == null || !DepositRoutine.SafeLoad(container))
			{
				return;
			}
			ReferenceSlots.ReadFilters(container, Filters);
			if (Filters.Count == 0)
			{
				Diagnose("chest is set up but has no filter - put an item in " + ReferenceSlots.Describe(ReferenceSlots.CountFor(container)));
				return;
			}
			foreach (string filter in Filters)
			{
				bool reserveBound;
				int num = BudgetFor(inventory, bag, filter, out reserveBound);
				if (num <= 0)
				{
					if (Plugin.DebugLogging.Value)
					{
						Diagnose(reserveBound ? ("keeping your " + Localize(filter) + ": reserve of " + $"{InventoryReserve.For(filter)} not exceeded (KeepInInventory)") : $"stack limit reached for {Localize(filter)} ({Plugin.StackLimit.Value})");
					}
				}
				else
				{
					DepositMatching(container, bag, filter, num);
				}
			}
		}

		private static int RemainingAllowance(Inventory chest, string filter)
		{
			int value = Plugin.StackLimit.Value;
			if (value <= 0)
			{
				return int.MaxValue;
			}
			int num = chest.CountItems(filter, -1, false);
			return value - num;
		}

		private static int BudgetFor(Inventory chest, Inventory bag, string filter, out bool reserveBound)
		{
			int num = RemainingAllowance(chest, filter);
			int num2 = InventoryReserve.Spare(bag, filter);
			reserveBound = num2 < num;
			return Math.Min(num, num2);
		}

		private static ItemSkip WhySkip(ItemData item)
		{
			if (item == null || item.m_shared == null)
			{
				return ItemSkip.Invalid;
			}
			if (item.m_shared.m_maxStackSize <= 1)
			{
				return ItemSkip.Unique;
			}
			if (item.m_equipped)
			{
				return ItemSkip.Equipped;
			}
			if (Plugin.IgnoreHotbar.Value && item.m_gridPos.y == 0)
			{
				return ItemSkip.Hotbar;
			}
			return ItemSkip.None;
		}

		private static void DepositMatching(Container container, Inventory bag, string filter, int budget)
		{
			Candidates.Clear();
			Candidates.AddRange(bag.GetAllItems());
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			foreach (ItemData candidate in Candidates)
			{
				if (budget <= 0)
				{
					return;
				}
				if (candidate == null || candidate.m_shared == null || candidate.m_shared.m_name != filter)
				{
					continue;
				}
				num++;
				switch (WhySkip(candidate))
				{
				case ItemSkip.Unique:
					num4++;
					continue;
				case ItemSkip.Equipped:
					num3++;
					continue;
				case ItemSkip.Hotbar:
					num2++;
					continue;
				case ItemSkip.Invalid:
					continue;
				}
				int num5 = Math.Min(candidate.m_stack, budget);
				if (num5 > 0)
				{
					int num6 = DepositRoutine.TryDeposit(container, bag, candidate, num5);
					if (num6 > 0)
					{
						budget -= num6;
						Accumulate(filter, num6);
					}
				}
			}
			if (Moved.Count <= 0 && Plugin.DebugLogging.Value)
			{
				string text = Localize(filter);
				if (num == 0)
				{
					Diagnose("nothing to store: you are not carrying any " + text);
				}
				else if (num2 > 0 && num2 + num3 == num)
				{
					Diagnose($"{num2} stack(s) of {text} skipped, they are on the hotbar " + "(first row) - move them one row down, or set IgnoreHotbar to false");
				}
				else if (num4 == num)
				{
					Diagnose(text + " does not stack - the mod only stores stackable items");
				}
				else if (num3 == num)
				{
					Diagnose(text + " is equipped, never taken");
				}
			}
		}

		private static void Accumulate(string filter, int amount)
		{
			Moved.TryGetValue(filter, out var value);
			Moved[filter] = value + amount;
		}

		private static void Report(Player player)
		{
			Pending.Clear();
			LastPass.Clear();
			if (!Plugin.ShowMessages.Value)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder("Stored:");
			foreach (KeyValuePair<string, int> item in Moved)
			{
				stringBuilder.Append('\n').Append(Localize(item.Key)).Append(" x")
					.Append(item.Value);
			}
			string text = stringBuilder.ToString();
			((Character)player).Message((MessageType)1, text, 0, (Sprite)null, false);
			Plugin.Log.LogInfo((object)text);
		}

		private static void Diagnose(string reason)
		{
			if (Plugin.DebugLogging.Value)
			{
				Pending.Add(reason);
			}
		}

		private static void FlushDiagnostics()
		{
			if (Pending.Count > 0 && !SameAsLastPass())
			{
				foreach (string item in Pending)
				{
					Plugin.Log.LogInfo((object)("[loop] " + item));
				}
				LastPass.Clear();
				LastPass.AddRange(Pending);
			}
			Pending.Clear();
		}

		private static bool SameAsLastPass()
		{
			if (Pending.Count != LastPass.Count)
			{
				return false;
			}
			for (int i = 0; i < Pending.Count; i++)
			{
				if (!string.Equals(Pending[i], LastPass[i], StringComparison.Ordinal))
				{
					return false;
				}
			}
			return true;
		}

		private static string Localize(string token)
		{
			Localization instance = Localization.instance;
			if (instance == null)
			{
				return token;
			}
			return instance.Localize(token);
		}
	}
	internal static class AutostoreState
	{
		internal const string ZdoKey = "autostore_enabled";

		internal const string ZdoRefKeyPrefix = "autostore_ref_";

		internal const string ZdoSlotsKey = "autostore_slots";

		internal const string ZdoUidKey = "autostore_uid";

		internal static bool IsEnabled(Container container)
		{
			if (!Object.op_Implicit((Object)(object)container))
			{
				return false;
			}
			ZNetView nview = container.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
			{
				return false;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO == null)
			{
				return false;
			}
			return zDO.GetBool("autostore_enabled", false);
		}

		internal static long GetUid(Container container)
		{
			if (!Object.op_Implicit((Object)(object)container))
			{
				return 0L;
			}
			ZNetView nview = container.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
			{
				return 0L;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO == null)
			{
				return 0L;
			}
			return zDO.GetLong("autostore_uid", 0L);
		}

		internal static bool TryStamp(Container container, out long uid)
		{
			uid = 0L;
			if (!Object.op_Implicit((Object)(object)container))
			{
				return false;
			}
			ZNetView nview = container.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
			{
				return false;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO == null)
			{
				return false;
			}
			uid = zDO.GetLong("autostore_uid", 0L);
			if (uid != 0L)
			{
				return true;
			}
			if (!nview.IsOwner())
			{
				return false;
			}
			uid = MintUid();
			zDO.Set("autostore_uid", uid);
			return true;
		}

		private static long MintUid()
		{
			long num;
			do
			{
				num = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0);
			}
			while (num == 0L);
			return num;
		}

		internal static bool Cycle(Container container, out bool enabled, out int count)
		{
			enabled = false;
			count = 0;
			if (!Object.op_Implicit((Object)(object)container))
			{
				return false;
			}
			ZNetView nview = container.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
			{
				return false;
			}
			if (!nview.IsOwner())
			{
				return false;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO == null)
			{
				return false;
			}
			TryStamp(container, out var _);
			int num = ReferenceSlots.MaxFor(container);
			count = ReferenceSlots.CountFor(container);
			if (!IsEnabled(container))
			{
				enabled = true;
				count = 1;
			}
			else if (count >= num)
			{
				enabled = false;
			}
			else
			{
				enabled = true;
				count++;
			}
			zDO.Set("autostore_enabled", enabled);
			if (enabled)
			{
				zDO.Set("autostore_slots", count);
				ReferenceSlots.Remember(container, clearBeyond: false);
			}
			return true;
		}
	}
	internal struct ChestKey : IEquatable<ChestKey>
	{
		internal readonly long User;

		internal readonly uint Id;

		internal bool IsValid
		{
			get
			{
				if (User == 0L)
				{
					return Id != 0;
				}
				return true;
			}
		}

		internal ChestKey(long user, uint id)
		{
			User = user;
			Id = id;
		}

		internal static ChestKey From(ZDOID id)
		{
			return new ChestKey(((ZDOID)(ref id)).UserID, ((ZDOID)(ref id)).ID);
		}

		public bool Equals(ChestKey other)
		{
			if (User == other.User)
			{
				return Id == other.Id;
			}
			return false;
		}

		public override bool Equals(object obj)
		{
			if (obj is ChestKey other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			long user = User;
			int hashCode = user.GetHashCode();
			uint id = Id;
			return hashCode ^ id.GetHashCode();
		}

		public override string ToString()
		{
			return $"{User}:{Id}";
		}
	}
	internal static class ContainerFinder
	{
		private static readonly List<Container> Loaded = new List<Container>();

		internal static int TrackedCount => Loaded.Count;

		internal static void Register(Container container)
		{
			if (Object.op_Implicit((Object)(object)container))
			{
				Loaded.Add(container);
			}
		}

		internal static void Forget(Container container)
		{
			if (!((Object)(object)container == (Object)null))
			{
				Loaded.Remove(container);
			}
		}

		internal static bool TryGetKey(Container container, out ChestKey key)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			key = default(ChestKey);
			if (!TryGetId(container, out var id))
			{
				return false;
			}
			key = ChestKey.From(id);
			return true;
		}

		internal static bool TryGetId(Container container, out ZDOID id)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			id = ZDOID.None;
			if (!Object.op_Implicit((Object)(object)container))
			{
				return false;
			}
			ZNetView nview = container.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
			{
				return false;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO == null)
			{
				return false;
			}
			id = zDO.m_uid;
			return true;
		}

		internal static void FindInRange(Vector3 point, float radius, float vertical, List<Container> results)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			results.Clear();
			for (int num = Loaded.Count - 1; num >= 0; num--)
			{
				Container val = Loaded[num];
				if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)((Component)val).transform))
				{
					Loaded.RemoveAt(num);
				}
				else
				{
					ZNetView nview = val.m_nview;
					if (Object.op_Implicit((Object)(object)nview) && nview.IsValid() && nview.HasOwner() && Proximity.Within(point, ((Component)val).transform.position, radius, vertical))
					{
						results.Add(val);
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(Container))]
	internal static class ContainerTrackingPatch
	{
		[HarmonyPatch("Awake")]
		[HarmonyPostfix]
		private static void Awake(Container __instance)
		{
			ContainerFinder.Register(__instance);
		}

		[HarmonyPatch("OnDestroyed")]
		[HarmonyPostfix]
		private static void OnDestroyed(Container __instance)
		{
			ContainerFinder.Forget(__instance);
		}
	}
	internal static class DepositRoutine
	{
		private static readonly string[] Memory = new string[8];

		internal static int TryDeposit(Container container, Inventory source, ItemData item, int amount)
		{
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)container) || source == null || item == null)
			{
				return Refuse("null argument");
			}
			ZNetView nview = container.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
			{
				return Refuse("missing or invalid ZNetView");
			}
			if (!RequireOwnership(nview))
			{
				return Refuse("not the owner of this chest");
			}
			if (container.IsInUse())
			{
				return Refuse("chest is open");
			}
			if (!HasAccess(container))
			{
				return Refuse("access denied (private chest or ward)");
			}
			Inventory inventory = container.GetInventory();
			if (inventory == null)
			{
				return Refuse("chest inventory unreachable");
			}
			int refCount = ReferenceSlots.CountFor(container);
			ReferenceSlots.ReadMemory(container, Memory);
			if (!FindTargetSlot(inventory, item, refCount, Memory, out var _, out var _))
			{
				return Refuse("no free slot (chest full)");
			}
			uint dataRevision = nview.GetZDO().DataRevision;
			int num = DepositIntoInventory(inventory, source, item, amount, refCount, Memory);
			if (num > 0 && nview.GetZDO().DataRevision == dataRevision)
			{
				Plugin.Log.LogError((object)($"ANOMALY - {num} item(s) taken from the player without the chest saving: " + $"they are destroyed. Chest {nview.GetZDO().m_uid}. " + "Please report this, it is a serious bug in the mod."));
			}
			return num;
		}

		private static bool RequireOwnership(ZNetView nview)
		{
			return nview.IsOwner();
		}

		internal static bool SafeLoad(Container container)
		{
			if (!Object.op_Implicit((Object)(object)container))
			{
				return false;
			}
			try
			{
				container.Load();
				return true;
			}
			catch (Exception arg)
			{
				container.m_loading = false;
				container.m_lastRevision = uint.MaxValue;
				Plugin.Log.LogError((object)$"Could not reload chest, deposit aborted and cache invalidated: {arg}");
				return false;
			}
		}

		private static int Refuse(string reason)
		{
			return 0;
		}

		private static bool HasAccess(Container container)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer))
			{
				return false;
			}
			try
			{
				return container.CheckAccess(localPlayer.GetPlayerID());
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("CheckAccess failed: " + ex.Message));
				return false;
			}
		}

		internal static int DepositIntoInventory(Inventory target, Inventory source, ItemData item, int amount, int refCount, string[] memory)
		{
			if (target == null || source == null || item == null)
			{
				return 0;
			}
			int num = Math.Min(amount, item.m_stack);
			if (num <= 0)
			{
				return 0;
			}
			int num2 = 0;
			int x;
			int y;
			while (num > 0 && FindTargetSlot(target, item, refCount, memory, out x, out y))
			{
				int stack = item.m_stack;
				target.MoveItemToThis(source, item, num, x, y);
				int num3 = stack - item.m_stack;
				if (num3 <= 0)
				{
					break;
				}
				num2 += num3;
				num -= num3;
				if (item.m_stack <= 0)
				{
					break;
				}
			}
			return num2;
		}

		internal static bool HasRoomFor(Inventory chest, ItemData item, int refCount, string[] memory)
		{
			if (chest == null || item == null)
			{
				return false;
			}
			int x;
			int y;
			return FindTargetSlot(chest, item, refCount, memory, out x, out y);
		}

		private static bool FindTargetSlot(Inventory inventory, ItemData item, int refCount, string[] memory, out int x, out int y)
		{
			int width = inventory.GetWidth();
			int height = inventory.GetHeight();
			for (int i = 0; i < height; i++)
			{
				for (int j = 0; j < width; j++)
				{
					ItemData itemAt = inventory.GetItemAt(j, i);
					if (itemAt != null && CanStackOnto(itemAt, item))
					{
						x = j;
						y = i;
						return true;
					}
				}
			}
			for (int k = 0; k < height; k++)
			{
				for (int l = 0; l < width; l++)
				{
					if ((!ReferenceSlots.IsReference(refCount, l, k) || Remembers(memory, l, item)) && inventory.GetItemAt(l, k) == null)
					{
						x = l;
						y = k;
						return true;
					}
				}
			}
			x = -1;
			y = -1;
			return false;
		}

		private static bool Remembers(string[] memory, int col, ItemData item)
		{
			if (memory == null || col < 0 || col >= memory.Length)
			{
				return false;
			}
			if (item.m_shared == null)
			{
				return false;
			}
			string text = memory[col];
			if (!string.IsNullOrEmpty(text))
			{
				return text == item.m_shared.m_name;
			}
			return false;
		}

		private static bool CanStackOnto(ItemData slot, ItemData item)
		{
			if (slot.m_shared == null || item.m_shared == null)
			{
				return false;
			}
			if (slot.m_shared.m_name != item.m_shared.m_name)
			{
				return false;
			}
			if (slot.m_worldLevel != item.m_worldLevel)
			{
				return false;
			}
			if (slot.m_shared.m_maxQuality > 1 && slot.m_quality != item.m_quality)
			{
				return false;
			}
			return slot.m_stack < slot.m_shared.m_maxStackSize;
		}
	}
	internal static class DesignationInput
	{
		internal static void Update()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!Plugin.Enabled.Value)
				{
					return;
				}
				KeyboardShortcut value = Plugin.DesignateKey.Value;
				if (!((KeyboardShortcut)(ref value)).IsDown() || IsTypingText())
				{
					return;
				}
				InventoryGui instance = InventoryGui.m_instance;
				if (Object.op_Implicit((Object)(object)instance) && instance.IsContainerOpen())
				{
					Container currentContainer = instance.m_currentContainer;
					if (Object.op_Implicit((Object)(object)currentContainer))
					{
						Designate(currentContainer);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"DesignationInput threw: {arg}");
			}
		}

		internal static void Designate(Container container)
		{
			if (!AutostoreState.Cycle(container, out var enabled, out var count))
			{
				Plugin.Log.LogWarning((object)"Cannot toggle: chest not synced yet, or owned by another player.");
				Player localPlayer = Player.m_localPlayer;
				if (Object.op_Implicit((Object)(object)localPlayer))
				{
					((Character)localPlayer).Message((MessageType)2, "Chest unavailable - try again in a moment", 0, (Sprite)null, false);
				}
			}
			else
			{
				KnownContainers.Remember(container);
				Plugin.Log.LogInfo((object)(enabled ? $"Autostore enabled with {count} reference slot(s) - {container.m_name}" : ("Autostore disabled - " + container.m_name)));
			}
		}

		internal static bool IsTypingText()
		{
			if (Object.op_Implicit((Object)(object)Chat.m_instance) && Chat.m_instance.HasFocus())
			{
				return true;
			}
			if (Console.IsVisible())
			{
				return true;
			}
			if (TextInput.IsVisible())
			{
				return true;
			}
			if (Minimap.InTextInput())
			{
				return true;
			}
			return false;
		}
	}
	internal static class FormerGuid
	{
		private const string ConfigSuffix = ".valheim.autostore.cfg";

		private const string KnownListSuffix = ".valheim.autostore.known.v2.txt";

		private const string ConfigHeader = "## Settings file was created by plugin Autostore v";

		internal static void Adopt(ConfigFile config)
		{
			try
			{
				if (Copy(".valheim.autostore.cfg", config.ConfigFilePath, IsOurConfig))
				{
					config.Reload();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not carry over the former settings file (" + ex.Message + ")."));
			}
			try
			{
				Copy(".valheim.autostore.known.v2.txt", KnownContainers.FilePath, null);
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Could not carry over the former known-chest list (" + ex2.Message + ")."));
			}
		}

		private static bool Copy(string suffix, string target, Func<string, bool> accept)
		{
			if (File.Exists(target))
			{
				return false;
			}
			string text = FindSingle(suffix, accept);
			if (text == null)
			{
				return false;
			}
			File.Copy(text, target);
			Plugin.Log.LogInfo((object)("Carried over " + Path.GetFileName(text) + " as " + Path.GetFileName(target) + "."));
			return true;
		}

		private static string FindSingle(string suffix, Func<string, bool> accept)
		{
			string text = null;
			string[] files = Directory.GetFiles(Paths.ConfigPath, "*" + suffix);
			foreach (string text2 in files)
			{
				if (text2.EndsWith(suffix, StringComparison.OrdinalIgnoreCase) && (accept == null || accept(text2)))
				{
					if (text != null)
					{
						Plugin.Log.LogWarning((object)("Several former *" + suffix + " files, none carried over."));
						return null;
					}
					text = text2;
				}
			}
			return text;
		}

		private static bool IsOurConfig(string path)
		{
			using StreamReader streamReader = new StreamReader(path);
			return streamReader.ReadLine()?.StartsWith("## Settings file was created by plugin Autostore v", StringComparison.Ordinal) ?? false;
		}
	}
	internal static class InventoryReserve
	{
		internal const string AllKeyword = "all";

		private const int NoReserve = 0;

		private static readonly Dictionary<string, int> ByToken = new Dictionary<string, int>(StringComparer.Ordinal);

		private static int _all = 0;

		private static string _source;

		private static bool _pendingNames;

		internal static int For(string filter)
		{
			EnsureParsed();
			if (string.IsNullOrEmpty(filter))
			{
				return 0;
			}
			if (ByToken.TryGetValue(filter, out var value))
			{
				return value;
			}
			return _all;
		}

		internal static int Spare(Inventory bag, string filter)
		{
			int num = For(filter);
			if (num <= 0)
			{
				return int.MaxValue;
			}
			if (bag == null)
			{
				return int.MaxValue;
			}
			int num2 = bag.CountItems(filter, -1, false);
			return Math.Max(0, num2 - num);
		}

		private static void EnsureParsed()
		{
			string text = ((Plugin.KeepInInventory == null) ? string.Empty : (Plugin.KeepInInventory.Value ?? string.Empty));
			if (_pendingNames || !string.Equals(text, _source, StringComparison.Ordinal))
			{
				Parse(text);
			}
		}

		private static void Parse(string raw)
		{
			ByToken.Clear();
			_all = 0;
			_source = raw;
			_pendingNames = false;
			if (string.IsNullOrEmpty(raw))
			{
				return;
			}
			Dictionary<string, string> dictionary = null;
			bool flag = false;
			List<string> list = null;
			string[] array = raw.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length == 0)
				{
					continue;
				}
				if (!SplitEntry(text, out var name, out var amount))
				{
					(list ?? (list = new List<string>())).Add(text);
					continue;
				}
				if (string.Equals(name, "all", StringComparison.OrdinalIgnoreCase))
				{
					_all = amount;
					continue;
				}
				if (name[0] == '$')
				{
					ByToken[name] = amount;
					continue;
				}
				if (!flag)
				{
					dictionary = BuildNameMap();
					flag = true;
				}
				string value;
				if (dictionary == null)
				{
					_pendingNames = true;
				}
				else if (!dictionary.TryGetValue(name, out value))
				{
					(list ?? (list = new List<string>())).Add(text);
				}
				else
				{
					ByToken[value] = amount;
				}
			}
			Report(list);
		}

		private static bool SplitEntry(string entry, out string name, out int amount)
		{
			name = null;
			amount = 0;
			int num = entry.LastIndexOf(':');
			if (num <= 0 || num == entry.Length - 1)
			{
				return false;
			}
			name = entry.Substring(0, num).Trim();
			if (name.Length == 0)
			{
				return false;
			}
			if (!int.TryParse(entry.Substring(num + 1).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out amount))
			{
				return false;
			}
			if (amount < 0)
			{
				amount = 0;
			}
			return true;
		}

		private static Dictionary<string, string> BuildNameMap()
		{
			ObjectDB instance = ObjectDB.instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				return null;
			}
			List<GameObject> items = instance.m_items;
			if (items == null)
			{
				return null;
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>(items.Count * 2, StringComparer.OrdinalIgnoreCase);
			List<string> list = new List<string>(items.Count);
			foreach (GameObject item in items)
			{
				if (!Object.op_Implicit((Object)(object)item))
				{
					continue;
				}
				ItemDrop component = item.GetComponent<ItemDrop>();
				if (Object.op_Implicit((Object)(object)component) && component.m_itemData != null && component.m_itemData.m_shared != null)
				{
					string name = component.m_itemData.m_shared.m_name;
					if (!string.IsNullOrEmpty(name))
					{
						dictionary[((Object)item).name] = name;
						list.Add(name);
					}
				}
			}
			Localization instance2 = Localization.instance;
			if (instance2 != null)
			{
				foreach (string item2 in list)
				{
					string text = instance2.Localize(item2);
					if (!string.IsNullOrEmpty(text) && !dictionary.ContainsKey(text))
					{
						dictionary[text] = item2;
					}
				}
			}
			return dictionary;
		}

		private static void Report(List<string> rejected)
		{
			if (rejected != null && rejected.Count != 0)
			{
				Plugin.Log.LogWarning((object)("KeepInInventory: ignoring " + string.Join(", ", rejected.ToArray()) + ". Expected Item:Amount, with Item a prefab name like Wood, FineWood or Coins (or all for every item)."));
			}
		}
	}
	internal static class KnownContainers
	{
		private struct Waiting
		{
			internal Container Container;

			internal float Since;
		}

		private const string FileName = "moochi.autostore.known.v2.txt";

		private static readonly Dictionary<long, HashSet<long>> ByWorld = new Dictionary<long, HashSet<long>>();

		private static readonly Dictionary<ChestKey, Waiting> Pending = new Dictionary<ChestKey, Waiting>();

		private static readonly List<ChestKey> Settled = new List<ChestKey>();

		private static bool _loaded;

		private static float _nextLoadAttempt;

		internal static string FilePath => Path.Combine(Paths.ConfigPath, "moochi.autostore.known.v2.txt");

		private static float Now => Time.unscaledTime;

		internal static long CurrentWorldId()
		{
			ZNet instance = ZNet.instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				return 0L;
			}
			if (ZNet.m_world == null)
			{
				return 0L;
			}
			return instance.GetWorldUID();
		}

		internal static bool IsKnown(Container container)
		{
			long uid = AutostoreState.GetUid(container);
			if (uid == 0L)
			{
				return false;
			}
			long num = CurrentWorldId();
			if (num == 0L)
			{
				return false;
			}
			EnsureLoaded();
			if (ByWorld.TryGetValue(num, out var value))
			{
				return value.Contains(uid);
			}
			return false;
		}

		internal static void Remember(Container container)
		{
			if (!Plugin.Enabled.Value || !Object.op_Implicit((Object)(object)container))
			{
				return;
			}
			long uid = AutostoreState.GetUid(container);
			if (uid != 0L)
			{
				Note(uid);
			}
			else if (AutostoreState.IsEnabled(container))
			{
				if (AutostoreState.TryStamp(container, out uid))
				{
					Note(uid);
				}
				else
				{
					Enqueue(container);
				}
			}
		}

		internal static void Pump()
		{
			if (Pending.Count == 0)
			{
				return;
			}
			if (!Plugin.Enabled.Value)
			{
				Pending.Clear();
				return;
			}
			try
			{
				float num = Mathf.Max(0.5f, Plugin.HandshakeTimeout.Value);
				Settled.Clear();
				foreach (KeyValuePair<ChestKey, Waiting> item in Pending)
				{
					Container container = item.Value.Container;
					if (!Object.op_Implicit((Object)(object)container))
					{
						Settled.Add(item.Key);
						continue;
					}
					long uid = AutostoreState.GetUid(container);
					if (uid != 0L)
					{
						Note(uid);
						Settled.Add(item.Key);
					}
					else if (!AutostoreState.IsEnabled(container))
					{
						Settled.Add(item.Key);
					}
					else if (AutostoreState.TryStamp(container, out uid))
					{
						Note(uid);
						Settled.Add(item.Key);
					}
					else if (!(Now - item.Value.Since <= num))
					{
						Plugin.Log.LogWarning((object)"Chest opened but ownership never landed - it stays unenrolled, open it again.");
						Settled.Add(item.Key);
					}
				}
				foreach (ChestKey item2 in Settled)
				{
					Pending.Remove(item2);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"KnownContainers.Pump threw: {arg}");
				Pending.Clear();
			}
		}

		internal static void ClearPending()
		{
			Pending.Clear();
		}

		private static void Enqueue(Container container)
		{
			if (ContainerFinder.TryGetKey(container, out var key) && !Pending.ContainsKey(key))
			{
				Pending[key] = new Waiting
				{
					Container = container,
					Since = Now
				};
				Trace($"{key} has no identity yet, waiting for ownership to land");
			}
		}

		private static void Note(long uid)
		{
			if (uid == 0L)
			{
				return;
			}
			long num = CurrentWorldId();
			if (num != 0L)
			{
				EnsureLoaded();
				if (!ByWorld.TryGetValue(num, out var value))
				{
					value = new HashSet<long>();
					ByWorld[num] = value;
				}
				if (value.Add(uid))
				{
					Plugin.Log.LogInfo((object)$"Chest enrolled: world {num}, chest {uid}");
					Append(num, uid);
				}
			}
		}

		private static void Trace(string message)
		{
			if (Plugin.DebugLogging != null && Plugin.DebugLogging.Value)
			{
				Plugin.Log.LogInfo((object)("[enrollment] " + message));
			}
		}

		private static void EnsureLoaded()
		{
			if (_loaded || Now < _nextLoadAttempt)
			{
				return;
			}
			_nextLoadAttempt = Now + 30f;
			try
			{
				if (!File.Exists(FilePath))
				{
					_loaded = true;
					return;
				}
				string[] array = File.ReadAllLines(FilePath);
				for (int i = 0; i < array.Length; i++)
				{
					if (TryParse(array[i], out var worldId, out var uid))
					{
						if (!ByWorld.TryGetValue(worldId, out var value))
						{
							value = new HashSet<long>();
							ByWorld[worldId] = value;
						}
						value.Add(uid);
					}
				}
				_loaded = true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Known-chest list unreadable (" + ex.Message + ") - chests will be relearned on opening, and the list will be read again in 30 s."));
			}
		}

		private static bool TryParse(string line, out long worldId, out long uid)
		{
			worldId = 0L;
			uid = 0L;
			if (string.IsNullOrEmpty(line))
			{
				return false;
			}
			string[] array = line.Split(new char[1] { ' ' });
			if (array.Length != 2)
			{
				return false;
			}
			if (!long.TryParse(array[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out worldId))
			{
				return false;
			}
			if (!long.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out uid))
			{
				return false;
			}
			return uid != 0;
		}

		private static void Append(long worldId, long uid)
		{
			try
			{
				string text = string.Format(CultureInfo.InvariantCulture, "{0} {1}", worldId, uid);
				File.AppendAllText(FilePath, text + Environment.NewLine);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not persist known chest (" + ex.Message + ")."));
			}
		}
	}
	internal static class MessagePlacement
	{
		private static RectTransform _target;

		private static Vector2 _origin;

		private static float _applied;

		internal static void Update()
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				float num = ((Plugin.MessageOffsetY == null) ? 0f : Plugin.MessageOffsetY.Value);
				if (Capture() && !Mathf.Approximately(num, _applied))
				{
					_target.anchoredPosition = new Vector2(_origin.x, _origin.y - num);
					_applied = num;
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"MessagePlacement threw: {arg}");
			}
		}

		private static bool Capture()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)_target))
			{
				return true;
			}
			MessageHud instance = MessageHud.instance;
			if (!Object.op_Implicit((Object)(object)instance) || !Object.op_Implicit((Object)(object)instance.m_messageText))
			{
				return false;
			}
			_target = instance.m_messageText.rectTransform;
			if (!Object.op_Implicit((Object)(object)_target))
			{
				return false;
			}
			_origin = _target.anchoredPosition;
			_applied = float.NaN;
			return true;
		}
	}
	internal static class OwnershipHandshake
	{
		private enum State
		{
			Idle,
			Pending,
			AwaitingOwnership
		}

		private const float SwallowGrace = 0.5f;

		private static State _state = State.Idle;

		private static Container _container;

		private static ChestKey _id;

		private static float _sentAt;

		private static readonly Dictionary<ChestKey, float> LastRequest = new Dictionary<ChestKey, float>();

		private static readonly Dictionary<ChestKey, float> Swallow = new Dictionary<ChestKey, float>();

		private static readonly List<ChestKey> Expired = new List<ChestKey>();

		private static float _nextSweep;

		private static float Now => Time.unscaledTime;

		internal static bool CanRequest(ChestKey id)
		{
			if (_state != State.Idle)
			{
				return false;
			}
			if (!LastRequest.TryGetValue(id, out var value))
			{
				return true;
			}
			return Now - value >= Mathf.Max(0f, Plugin.RequestCooldown.Value);
		}

		internal static bool ShouldSwallow(Container container)
		{
			if (!Object.op_Implicit((Object)(object)container))
			{
				return false;
			}
			if (!ContainerFinder.TryGetKey(container, out var key))
			{
				return false;
			}
			if (!Swallow.TryGetValue(key, out var value))
			{
				return false;
			}
			if (Now >= value)
			{
				Swallow.Remove(key);
				return false;
			}
			return true;
		}

		internal static void Request(Container container)
		{
			if (Object.op_Implicit((Object)(object)container) && ContainerFinder.TryGetKey(container, out var key) && CanRequest(key))
			{
				ZNetView nview = container.m_nview;
				if (Object.op_Implicit((Object)(object)nview) && nview.IsValid() && nview.HasOwner())
				{
					LastRequest[key] = Now;
					Swallow[key] = Now + Mathf.Max(0.5f, Plugin.HandshakeTimeout.Value) + 0.5f;
					_container = container;
					_id = key;
					_sentAt = Now;
					_state = State.Pending;
					Trace($"ownership requested for {key}, current owner {nview.GetZDO().GetOwner()}");
					nview.InvokeRPC("RPC_RequestOpen", new object[1] { Game.instance.GetPlayerProfile().GetPlayerID() });
				}
			}
		}

		internal static void OnAnswer(Container container, bool granted)
		{
			if (ContainerFinder.TryGetKey(container, out var key))
			{
				Swallow.Remove(key);
				if (_state == State.Idle || !key.Equals(_id))
				{
					Trace($"stale answer for {key} (granted={granted}), ignored");
					return;
				}
				Trace(granted ? $"ownership granted for {key}, waiting for it to land" : $"ownership REFUSED for {key} (chest in use, or access denied)");
				_state = (granted ? State.AwaitingOwnership : State.Idle);
			}
		}

		internal static void Pump()
		{
			try
			{
				Sweep();
				if (_state != State.AwaitingOwnership)
				{
					return;
				}
				if (!Object.op_Implicit((Object)(object)_container))
				{
					Reset();
					return;
				}
				ZNetView nview = _container.m_nview;
				if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
				{
					Reset();
				}
				else if (nview.IsOwner())
				{
					Container container = _container;
					Trace($"ownership landed after {Now - _sentAt:0.###} s, completing deposit");
					Reset();
					AutostoreLoop.CompleteDeposit(container);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"OwnershipHandshake.Pump threw: {arg}");
				Reset();
			}
		}

		internal static void Watchdog()
		{
			if (_state != State.Idle && !(Now - _sentAt <= Mathf.Max(0.5f, Plugin.HandshakeTimeout.Value)))
			{
				Trace($"timed out in state {_state} after {Now - _sentAt:0.###} s - chest skipped for now");
				Reset();
			}
		}

		private static void Trace(string message)
		{
			if (Plugin.DebugLogging != null && Plugin.DebugLogging.Value)
			{
				Plugin.Log.LogInfo((object)("[handshake] " + message));
			}
		}

		internal static void Reset()
		{
			if (_id.IsValid)
			{
				Swallow.Remove(_id);
			}
			_state = State.Idle;
			_container = null;
			_id = default(ChestKey);
		}

		internal static void ClearRegisters()
		{
			LastRequest.Clear();
			Swallow.Clear();
		}

		internal static void AbandonFor(Container container)
		{
			if (Object.op_Implicit((Object)(object)container) && ContainerFinder.TryGetKey(container, out var key))
			{
				Swallow.Remove(key);
				if (_state != State.Idle && key.Equals(_id))
				{
					Trace($"player interacted with {key}, handshake abandoned");
					Reset();
				}
			}
		}

		private static void Sweep()
		{
			if (Now < _nextSweep)
			{
				return;
			}
			_nextSweep = Now + 30f;
			float num = Mathf.Max(0f, Plugin.RequestCooldown.Value);
			Expired.Clear();
			foreach (KeyValuePair<ChestKey, float> item in LastRequest)
			{
				if (Now - item.Value >= num)
				{
					Expired.Add(item.Key);
				}
			}
			foreach (ChestKey item2 in Expired)
			{
				LastRequest.Remove(item2);
			}
			Expired.Clear();
			foreach (KeyValuePair<ChestKey, float> item3 in Swallow)
			{
				if (Now >= item3.Value)
				{
					Expired.Add(item3.Key);
				}
			}
			foreach (ChestKey item4 in Expired)
			{
				Swallow.Remove(item4);
			}
		}
	}
	[BepInPlugin("moochi.autostore", "AutoStore", "0.4.6")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "moochi.autostore";

		public const string PluginName = "AutoStore";

		public const string PluginVersion = "0.4.6";

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> SearchRadius;

		internal static ConfigEntry<float> VerticalRange;

		internal static ConfigEntry<float> CheckInterval;

		internal static ConfigEntry<int> StackLimit;

		internal static ConfigEntry<KeyboardShortcut> DesignateKey;

		internal static ConfigEntry<KeyboardShortcut> ToggleKey;

		internal static ConfigEntry<bool> ShowMessages;

		internal static ConfigEntry<float> MessageOffsetY;

		internal static ConfigEntry<bool> IgnoreHotbar;

		internal static ConfigEntry<string> KeepInInventory;

		internal static ConfigEntry<float> RearmDistance;

		internal static ConfigEntry<float> RearmDelay;

		internal static ConfigEntry<float> HandshakeTimeout;

		internal static ConfigEntry<float> RequestCooldown;

		internal static ConfigEntry<bool> DebugLogging;

		private Harmony _harmony;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			FormerGuid.Adopt(((BaseUnityPlugin)this).Config);
			BindConfig();
			_harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "moochi.autostore");
			Log.LogInfo((object)(string.Format("{0} v{1} loaded - reach {2} m around ", "AutoStore", "0.4.6", SearchRadius.Value) + $"and {VerticalRange.Value} m up or down, " + "limit " + ((StackLimit.Value == 0) ? "unlimited" : StackLimit.Value.ToString()) + "."));
		}

		private void Update()
		{
			DesignationInput.Update();
			ToggleInput.Update();
			OwnershipHandshake.Watchdog();
			OwnershipHandshake.Pump();
			KnownContainers.Pump();
			MessagePlacement.Update();
			AutostoreLoop.Update();
		}

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

		private void BindConfig()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Expected O, but got Unknown
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Expected O, but got Unknown
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Expected O, but got Unknown
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Expected O, but got Unknown
			//IL_023d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: Expected O, but got Unknown
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0284: Expected O, but got Unknown
			//IL_02b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c1: Expected O, but got Unknown
			//IL_02f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fe: Expected O, but got Unknown
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turns the autostore on or off.");
			SearchRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "SearchRadius", 2f, new ConfigDescription("How close you must be, in meters, for an autostore chest to pull items from you. Measured flat on the ground, as on a map: how high the chest sits is handled on its own by VerticalRange.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 50f), Array.Empty<object>()));
			VerticalRange = ((BaseUnityPlugin)this).Config.Bind<float>("General", "VerticalRange", 4f, new ConfigDescription("How far above or below you, in meters, a chest may sit and still pull items. This is the height that goes with SearchRadius: a chest on a shelf over your head, or the top one of a stack, is close on the ground but high up. The default of 4 m covers a whole storey, so a chest right above or below you does reach you through the floor. Lower it to about 2 m to keep each floor to itself.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 50f), Array.Empty<object>()));
			CheckInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CheckInterval", 0.5f, new ConfigDescription("Seconds between two scans of nearby chests. Lower is more responsive but costs more.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), Array.Empty<object>()));
			StackLimit = ((BaseUnityPlugin)this).Config.Bind<int>("General", "StackLimit", 0, new ConfigDescription("Maximum amount of one item a chest will accept. 0 means unlimited: fill it up.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000), Array.Empty<object>()));
			DesignateKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "DesignateKey", new KeyboardShortcut((KeyCode)113, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Shortcut, with a chest open, that does what the AStr button of its window does: each press moves the chest one step along OFF, 1, 2... reference slots, up to the width of its top row, then back to OFF. The reference slots are the first slots of the top row, and whatever sits in them decides what the chest accepts. The chest remembers what each of them held: take the whole stack, or let another mod take it, and it still fills that slot with the same item next time. Put something else in a slot to change its filter; lower the count and close the chest to forget one. The count belongs to the chest and is shared with every player.");
			ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "ToggleKey", new KeyboardShortcut((KeyCode)287, Array.Empty<KeyCode>()), "Key that turns the whole mod off, and back on, while playing. It flips the Enabled setting above, so the mod stays off until you press it again, even after a restart. A message in the middle of the screen confirms each press. Does nothing while the map is open.");
			ShowMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("Feedback", "ShowMessages", true, "Show an on-screen message when items are stored automatically.");
			MessageOffsetY = ((BaseUnityPlugin)this).Config.Bind<float>("Feedback", "MessageOffsetY", 50f, new ConfigDescription("Move the top-left message area down, in pixels. Use it when something covers those messages, such as a hotbar moved up there by another mod. This shifts EVERY message in that corner, the game's own included. Takes effect right away, so you can tune it while playing. 0 leaves the game untouched.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 600f), Array.Empty<object>()));
			IgnoreHotbar = ((BaseUnityPlugin)this).Config.Bind<bool>("Filters", "IgnoreHotbar", true, "Never take items sitting on the hotbar (the first row of your inventory).");
			KeepInInventory = ((BaseUnityPlugin)this).Config.Bind<string>("Filters", "KeepInInventory", "", "How much of an item to always keep on you. Chests only take the surplus above it. Format: one Item:Amount entry per item, separated by commas, for example Wood:20, Stone:10, Coins:100. Name items the way the console does (Wood, FineWood, ArrowWood, Coins); the name shown in game works too. Write all:10 to set a default for every item, and Item:0 to exempt one item from that default. Everything you carry counts towards the reserve, including your hotbar and what you have equipped. Leave empty to keep nothing back.");
			RearmDistance = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RearmDistance", 4f, new ConfigDescription("How far you must walk away, in meters, before a chest can store again. A chest that just stored stays idle until you leave this radius - that is what lets you take an item back out without it going straight back in. Always forced above SearchRadius.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>()));
			RearmDelay = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RearmDelay", 5f, new ConfigDescription("Seconds before a chest that just stored becomes active again. The countdown only starts once you have left RearmDistance, then runs on its own: coming back before it ends does not stop it. 0 means the chest wakes up as soon as you have left.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>()));
			HandshakeTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("Multiplayer", "HandshakeTimeout", 2f, new ConfigDescription("Seconds to wait after asking another player for ownership of a chest. Past that, the chest is skipped until the next attempt. The owner may legitimately never answer if they stopped being the owner in the meantime.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>()));
			RequestCooldown = ((BaseUnityPlugin)this).Config.Bind<float>("Multiplayer", "RequestCooldown", 2f, new ConfigDescription("Minimum seconds between two ownership requests for the same chest. Keeps the traffic and the noise in the owner's log down, including when they do not have the mod.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 30f), Array.Empty<object>()));
			DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLogging", false, "Log why the proximity loop is not storing anything: range, designation, enrollment, filters, stack limit, rearming, hotbar exclusions. Messages are deduplicated - only a change of state is written, not one per scan. Turn this on to find out why a chest is not storing.");
		}
	}
	internal static class Proximity
	{
		internal static bool Within(Vector3 point, Vector3 target, float radius, float vertical)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: 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_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if (Mathf.Abs(target.y - point.y) > vertical)
			{
				return false;
			}
			float num = target.x - point.x;
			float num2 = target.z - point.z;
			return num * num + num2 * num2 <= radius * radius;
		}

		internal static float Flat(Vector3 point, Vector3 target)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			float num = target.x - point.x;
			float num2 = target.z - point.z;
			return Mathf.Sqrt(num * num + num2 * num2);
		}

		internal static float Rise(Vector3 point, Vector3 target)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return target.y - point.y;
		}
	}
	internal static class ReferenceSlots
	{
		internal const int Row = 0;

		internal const int LegacyCount = 2;

		internal const int MaxCount = 8;

		private static readonly string[] MemoryKeys = BuildMemoryKeys();

		private static readonly string[] ScratchMemory = new string[8];

		private static string[] BuildMemoryKeys()
		{
			string[] array = new string[8];
			for (int i = 0; i < 8; i++)
			{
				array[i] = "autostore_ref_" + i;
			}
			return array;
		}

		internal static int MaxFor(Container container)
		{
			if (!Object.op_Implicit((Object)(object)container))
			{
				return 8;
			}
			Inventory inventory = container.GetInventory();
			if (inventory == null)
			{
				return 8;
			}
			int width = inventory.GetWidth();
			if (width <= 0)
			{
				return 8;
			}
			return Mathf.Clamp(width, 1, 8);
		}

		internal static int CountFor(Container container)
		{
			if (!Object.op_Implicit((Object)(object)container))
			{
				return 2;
			}
			ZNetView nview = container.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
			{
				return 2;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO == null)
			{
				return 2;
			}
			return Mathf.Clamp(zDO.GetInt("autostore_slots", 2), 1, 8);
		}

		internal static bool IsReference(int count, int x, int y)
		{
			if (y == 0 && x >= 0)
			{
				return x < count;
			}
			return false;
		}

		internal static bool IsWithinGrid(Inventory inventory, int x, int y)
		{
			if (x >= 0 && y >= 0 && x < inventory.GetWidth())
			{
				return y < inventory.GetHeight();
			}
			return false;
		}

		internal static void ReadMemory(Container container, string[] into)
		{
			for (int i = 0; i < into.Length; i++)
			{
				into[i] = null;
			}
			if (!Object.op_Implicit((Object)(object)container))
			{
				return;
			}
			ZNetView nview = container.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
			{
				return;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO == null)
			{
				return;
			}
			int num = Mathf.Min(CountFor(container), into.Length);
			for (int j = 0; j < num; j++)
			{
				string text = zDO.GetString(MemoryKeys[j], "");
				if (text.Length > 0)
				{
					into[j] = text;
				}
			}
		}

		internal static void ReadFilters(Container container, List<string> into)
		{
			into.Clear();
			if (!Object.op_Implicit((Object)(object)container))
			{
				return;
			}
			Inventory inventory = container.GetInventory();
			if (inventory != null)
			{
				ReadMemory(container, ScratchMemory);
				int num = CountFor(container);
				for (int i = 0; i < num; i++)
				{
					AddFilter(inventory, i, ScratchMemory[i], into);
				}
			}
		}

		private static void AddFilter(Inventory chest, int x, string remembered, List<string> into)
		{
			string text = remembered;
			if (IsWithinGrid(chest, x, 0))
			{
				ItemData itemAt = chest.GetItemAt(x, 0);
				if (itemAt != null && itemAt.m_shared != null && !string.IsNullOrEmpty(itemAt.m_shared.m_name))
				{
					text = itemAt.m_shared.m_name;
				}
			}
			if (!string.IsNullOrEmpty(text) && !into.Contains(text))
			{
				into.Add(text);
			}
		}

		internal static void Remember(Container container, bool clearBeyond)
		{
			if (!Object.op_Implicit((Object)(object)container) || Plugin.Enabled == null || !Plugin.Enabled.Value)
			{
				return;
			}
			ZNetView nview = container.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid() || !nview.IsOwner())
			{
				return;
			}
			ZDO zDO = nview.GetZDO();
			if (zDO == null || !zDO.GetBool("autostore_enabled", false))
			{
				return;
			}
			Inventory inventory = container.GetInventory();
			if (inventory == null)
			{
				return;
			}
			int num = CountFor(container);
			for (int i = 0; i < 8; i++)
			{
				string text = MemoryKeys[i];
				if (i >= num)
				{
					if (!clearBeyond)
					{
						break;
					}
					if (zDO.GetString(text, "").Length > 0)
					{
						zDO.Set(text, "");
					}
				}
				else
				{
					if (!IsWithinGrid(inventory, i, 0))
					{
						continue;
					}
					ItemData itemAt = inventory.GetItemAt(i, 0);
					if (itemAt != null && itemAt.m_shared != null)
					{
						string name = itemAt.m_shared.m_name;
						if (!string.IsNullOrEmpty(name) && !(zDO.GetString(text, "") == name))
						{
							zDO.Set(text, name);
						}
					}
				}
			}
		}

		internal static string Describe(int count)
		{
			if (count > 1)
			{
				return $"(0,0) to ({count - 1},0)";
			}
			return "(0,0)";
		}
	}
	internal static class ToggleInput
	{
		private const float ReminderDelay = 4f;

		private static float _playerSince = -1f;

		private static bool _reminded;

		internal static void Update()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Player localPlayer = Player.m_localPlayer;
				Remind(localPlayer);
				if (IsPressed(Plugin.ToggleKey.Value) && !DesignationInput.IsTypingText() && !Minimap.IsOpen() && Object.op_Implicit((Object)(object)localPlayer))
				{
					bool flag = !Plugin.Enabled.Value;
					Plugin.Enabled.Value = flag;
					string text = (flag ? "AutoStore on" : "AutoStore off");
					((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false);
					Plugin.Log.LogInfo((object)$"{text} ({Plugin.ToggleKey.Value})");
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"ToggleInput threw: {arg}");
			}
		}

		private static void Remind(Player player)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)player))
			{
				_playerSince = -1f;
				_reminded = false;
				return;
			}
			if (_playerSince < 0f)
			{
				_playerSince = Time.time;
			}
			if (!_reminded && !(Time.time - _playerSince < 4f))
			{
				_reminded = true;
				if (!Plugin.Enabled.Value)
				{
					string text = $"AutoStore is off. Press {Plugin.ToggleKey.Value} to turn it on.";
					((Character)player).Message((MessageType)2, text, 0, (Sprite)null, false);
					Plugin.Log.LogInfo((object)text);
				}
			}
		}

		private static bool IsPressed(KeyboardShortcut shortcut)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0)
			{
				return false;
			}
			if (!ZInput.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey, false))
			{
				return false;
			}
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers)
			{
				if (!ZInput.GetKey(modifier, false))
				{
					return false;
				}
			}
			return true;
		}
	}
}
namespace ValheimAutostore.Patches
{
	[HarmonyPatch(typeof(Container), "OnContainerChanged")]
	internal static class ContainerChangedPatch
	{
		private static void Postfix(Container __instance)
		{
			try
			{
				if (Object.op_Implicit((Object)(object)__instance) && !__instance.m_loading)
				{
					ReferenceSlots.Remember(__instance, clearBeyond: false);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"ContainerChangedPatch threw: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(Container), "Interact", new Type[]
	{
		typeof(Humanoid),
		typeof(bool),
		typeof(bool)
	})]
	internal static class ContainerInteractPatch
	{
		private static readonly string[] Memory = new string[8];

		private static void Prefix(Container __instance, bool hold)
		{
			try
			{
				if (!hold)
				{
					OwnershipHandshake.AbandonFor(__instance);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"ContainerInteractPatch.Prefix threw: {arg}");
			}
		}

		private static void Postfix(Container __instance, bool hold, bool alt)
		{
			try
			{
				if (Plugin.Enabled.Value && Plugin.DebugLogging != null && Plugin.DebugLogging.Value && !hold)
				{
					LogReferenceSlots(__instance, alt);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"ContainerInteractPatch threw: {arg}");
			}
		}

		private static void LogReferenceSlots(Container container, bool alt)
		{
			if (!Object.op_Implicit((Object)(object)container))
			{
				return;
			}
			Inventory inventory = container.GetInventory();
			if (inventory != null)
			{
				Plugin.Log.LogInfo((object)($"Chest \"{container.m_name}\" - grid {inventory.GetWidth()}x{inventory.GetHeight()}, " + $"{inventory.NrOfItems()} item(s), alt={alt}"));
				int num = ReferenceSlots.CountFor(container);
				ReferenceSlots.ReadMemory(container, Memory);
				for (int i = 0; i < num; i++)
				{
					Describe(inventory, i, 0, Memory[i]);
				}
				LogLayout(inventory);
			}
		}

		private static void Describe(Inventory inventory, int x, int y, string remembered)
		{
			if (x >= inventory.GetWidth() || y >= inventory.GetHeight())
			{
				Plugin.Log.LogWarning((object)$"  slot ({x},{y}): outside the grid - this chest is too small.");
				return;
			}
			ItemData itemAt = inventory.GetItemAt(x, y);
			if (itemAt == null)
			{
				Plugin.Log.LogInfo((object)(string.IsNullOrEmpty(remembered) ? $"  slot ({x},{y}): empty, no filter." : $"  slot ({x},{y}): empty, remembers {remembered}."));
				return;
			}
			Plugin.Log.LogInfo((object)($"  slot ({x},{y}): {itemAt.m_shared.m_name} x{itemAt.m_stack}/{itemAt.m_shared.m_maxStackSize} " + $"- quality {itemAt.m_quality}, variant {itemAt.m_variant}"));
		}

		private static void LogLayout(Inventory inventory)
		{
			List<ItemData> allItems = inventory.GetAllItems();
			if (allItems == null || allItems.Count == 0)
			{
				Plugin.Log.LogInfo((object)"  layout: chest is empty.");
				return;
			}
			StringBuilder stringBuilder = new StringBuilder("  layout:");
			foreach (ItemData item in allItems)
			{
				if (item != null && item.m_shared != null)
				{
					stringBuilder.Append($" ({item.m_gridPos.x},{item.m_gridPos.y})={item.m_shared.m_name}x{item.m_stack}");
				}
			}
			Plugin.Log.LogInfo((object)stringBuilder.ToString());
		}
	}
	[HarmonyPatch(typeof(Container), "RPC_OpenResponse")]
	internal static class ContainerOpenPatch
	{
		private static bool Prefix(Container __instance, bool granted, out bool __state)
		{
			__state = false;
			try
			{
				if (!OwnershipHandshake.ShouldSwallow(__instance))
				{
					return true;
				}
				__state = true;
				OwnershipHandshake.OnAnswer(__instance, granted);
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"ContainerOpenPatch.Prefix threw: {arg}");
				__state = false;
				return true;
			}
		}

		private static void Postfix(Container __instance, bool granted, bool __state)
		{
			try
			{
				if (!__state && granted)
				{
					KnownContainers.Remember(__instance);
					AutostoreLoop.MarkServed(__instance);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"ContainerOpenPatch threw: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "CloseContainer")]
	internal static class InventoryGuiCloseContainerPatch
	{
		private static void Prefix(InventoryGui __instance)
		{
			try
			{
				if (Object.op_Implicit((Object)(object)__instance))
				{
					Container currentContainer = __instance.m_currentContainer;
					if (Object.op_Implicit((Object)(object)currentContainer))
					{
						ReferenceSlots.Remember(currentContainer, clearBeyond: true);
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"InventoryGuiCloseContainerPatch threw: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateContainer")]
	internal static class InventoryGuiUpdateContainerPatch
	{
		private static void Postfix(InventoryGui __instance)
		{
			try
			{
				if (Object.op_Implicit((Object)(object)__instance))
				{
					AutostoreButton.Refresh(__instance);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"InventoryGuiUpdateContainerPatch threw: {arg}");
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}