Decompiled source of RunicStorage v1.0.0

RunicStorage.dll

Decompiled 9 hours ago
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Runic.Foundation.Core;
using RunicStorage.Engine;
using RunicStorage.Runtime;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Runic Storage")]
[assembly: AssemblyDescription("Secure cached chest hover and player-directed authorized storage tools for Valheim.")]
[assembly: AssemblyCompany("Chazman")]
[assembly: AssemblyProduct("Runic Storage")]
[assembly: ComVisible(false)]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("1.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 Runic.Foundation.Core
{
	public static class RunicIdentifier
	{
		public static bool IsValid(string value)
		{
			if (string.IsNullOrEmpty(value) || value.Length > 128)
			{
				return false;
			}
			bool flag = false;
			bool flag2 = false;
			foreach (char c in value)
			{
				if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
				{
					flag = true;
					flag2 = false;
					continue;
				}
				switch (c)
				{
				case '-':
					if (!flag || flag2)
					{
						return false;
					}
					flag2 = true;
					break;
				case '.':
					if (!flag || flag2)
					{
						return false;
					}
					flag = false;
					flag2 = false;
					break;
				default:
					return false;
				}
			}
			if (flag)
			{
				return !flag2;
			}
			return false;
		}

		public static string Require(string value, string parameterName)
		{
			if (!IsValid(value))
			{
				throw new ArgumentException("Runic identifiers must be lowercase dot-separated tokens containing only ASCII letters, digits, and internal hyphens.", parameterName);
			}
			return value;
		}
	}
	public sealed class InputChord : IEquatable<InputChord>
	{
		private readonly IReadOnlyList<string> _modifiers;

		public string DeviceId { get; }

		public string PrimaryControl { get; }

		public IReadOnlyList<string> Modifiers => _modifiers;

		public string CanonicalId { get; }

		public InputChord(string deviceId, string primaryControl, IEnumerable<string> modifiers = null)
		{
			DeviceId = RunicIdentifier.Require(deviceId, "deviceId");
			PrimaryControl = RequireControl(primaryControl, "primaryControl");
			string text = CanonicalizeControl(PrimaryControl);
			SortedDictionary<string, string> sortedDictionary = new SortedDictionary<string, string>(StringComparer.Ordinal);
			if (modifiers != null)
			{
				foreach (string modifier in modifiers)
				{
					string text2 = RequireControl(modifier, "modifiers");
					string text3 = CanonicalizeControl(text2);
					if (string.Equals(text3, text, StringComparison.Ordinal))
					{
						throw new ArgumentException("The primary control cannot also be a modifier.", "modifiers");
					}
					if (sortedDictionary.ContainsKey(text3))
					{
						throw new ArgumentException("Duplicate input modifier: " + text2, "modifiers");
					}
					sortedDictionary.Add(text3, text2);
				}
			}
			string[] array = new string[sortedDictionary.Count];
			int num = 0;
			foreach (string value in sortedDictionary.Values)
			{
				array[num++] = value;
			}
			_modifiers = Array.AsReadOnly(array);
			StringBuilder stringBuilder = new StringBuilder(DeviceId).Append(':');
			foreach (string key in sortedDictionary.Keys)
			{
				stringBuilder.Append(key).Append('+');
			}
			stringBuilder.Append(text);
			CanonicalId = stringBuilder.ToString();
		}

		public bool Equals(InputChord other)
		{
			if (other != null)
			{
				return string.Equals(CanonicalId, other.CanonicalId, StringComparison.Ordinal);
			}
			return false;
		}

		public override bool Equals(object obj)
		{
			return Equals(obj as InputChord);
		}

		public override int GetHashCode()
		{
			return StringComparer.Ordinal.GetHashCode(CanonicalId);
		}

		public override string ToString()
		{
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < _modifiers.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append(" + ");
				}
				stringBuilder.Append(_modifiers[i]);
			}
			if (_modifiers.Count > 0)
			{
				stringBuilder.Append(" + ");
			}
			return stringBuilder.Append(PrimaryControl).Append(" [").Append(DeviceId)
				.Append(']')
				.ToString();
		}

		private static string RequireControl(string value, string parameterName)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				throw new ArgumentException("An input control name is required.", parameterName);
			}
			string text = value.Trim();
			if (text.Length > 64)
			{
				throw new ArgumentException("Input control names cannot exceed 64 characters.", parameterName);
			}
			string text2 = text;
			foreach (char c in text2)
			{
				if (char.IsControl(c) || c == ':' || c == '+' || c == '|')
				{
					throw new ArgumentException("The input control name contains a reserved character.", parameterName);
				}
			}
			if (CanonicalizeControl(text).Length == 0)
			{
				throw new ArgumentException("The input control name has no canonical characters.", parameterName);
			}
			return text;
		}

		private static string CanonicalizeControl(string value)
		{
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			foreach (char c in value)
			{
				if (!char.IsWhiteSpace(c) && c != '_' && c != '-')
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			return stringBuilder.ToString();
		}
	}
	public sealed class KeybindingDescriptor
	{
		public string ModuleId { get; }

		public string BindingId { get; }

		public string QualifiedId => ModuleId + "/" + BindingId;

		public string DisplayName { get; }

		public InputChord Chord { get; }

		public string Context { get; }

		public KeybindingDescriptor(string moduleId, string bindingId, string displayName, InputChord chord, string context = "gameplay")
		{
			ModuleId = RunicIdentifier.Require(moduleId, "moduleId");
			BindingId = RunicIdentifier.Require(bindingId, "bindingId");
			Context = RunicIdentifier.Require(context, "context");
			if (string.IsNullOrWhiteSpace(displayName))
			{
				throw new ArgumentException("A keybinding display name is required.", "displayName");
			}
			Chord = chord ?? throw new ArgumentNullException("chord");
			DisplayName = displayName.Trim();
		}
	}
	public sealed class KeybindingConflict
	{
		public InputChord Chord { get; }

		public IReadOnlyList<KeybindingDescriptor> Bindings { get; }

		internal KeybindingConflict(InputChord chord, IReadOnlyList<KeybindingDescriptor> bindings)
		{
			Chord = chord;
			Bindings = bindings;
		}
	}
	public enum KeybindingChangeKind
	{
		Registered,
		Unregistered
	}
	public sealed class KeybindingsChangedEventArgs : EventArgs
	{
		public KeybindingChangeKind Kind { get; }

		public KeybindingDescriptor Binding { get; }

		public KeybindingConflict Conflict { get; }

		internal KeybindingsChangedEventArgs(KeybindingChangeKind kind, KeybindingDescriptor binding, KeybindingConflict conflict)
		{
			Kind = kind;
			Binding = binding;
			Conflict = conflict;
		}
	}
	public sealed class KeybindingConflictRegistry
	{
		private sealed class BindingEntry
		{
			internal KeybindingDescriptor Binding { get; }

			internal long Token { get; }

			internal BindingEntry(KeybindingDescriptor binding, long token)
			{
				Binding = binding;
				Token = token;
			}
		}

		private readonly object _sync = new object();

		private readonly Dictionary<string, BindingEntry> _byQualifiedId = new Dictionary<string, BindingEntry>(StringComparer.Ordinal);

		private readonly Dictionary<string, List<BindingEntry>> _byChord = new Dictionary<string, List<BindingEntry>>(StringComparer.Ordinal);

		private long _nextToken;

		public event EventHandler<KeybindingsChangedEventArgs> Changed;

		public KeybindingRegistration Register(KeybindingDescriptor binding)
		{
			if (binding == null)
			{
				throw new ArgumentNullException("binding");
			}
			long token;
			KeybindingConflict conflict;
			lock (_sync)
			{
				if (_byQualifiedId.ContainsKey(binding.QualifiedId))
				{
					throw new InvalidOperationException("A keybinding is already registered as '" + binding.QualifiedId + "'.");
				}
				if (_nextToken == long.MaxValue)
				{
					throw new InvalidOperationException("The keybinding registration token space is exhausted.");
				}
				token = ++_nextToken;
				BindingEntry bindingEntry = new BindingEntry(binding, token);
				_byQualifiedId.Add(binding.QualifiedId, bindingEntry);
				if (!_byChord.TryGetValue(binding.Chord.CanonicalId, out var value))
				{
					value = new List<BindingEntry>();
					_byChord.Add(binding.Chord.CanonicalId, value);
				}
				value.Add(bindingEntry);
				conflict = CreateConflictLocked(binding.Chord.CanonicalId);
			}
			RaiseChanged(new KeybindingsChangedEventArgs(KeybindingChangeKind.Registered, binding, conflict));
			return new KeybindingRegistration(this, binding, token);
		}

		public bool Unregister(string moduleId, string bindingId)
		{
			RunicIdentifier.Require(moduleId, "moduleId");
			RunicIdentifier.Require(bindingId, "bindingId");
			return Unregister(moduleId + "/" + bindingId, (long?)null);
		}

		public bool TryGetBinding(string moduleId, string bindingId, out KeybindingDescriptor binding)
		{
			binding = null;
			if (!RunicIdentifier.IsValid(moduleId) || !RunicIdentifier.IsValid(bindingId))
			{
				return false;
			}
			lock (_sync)
			{
				if (!_byQualifiedId.TryGetValue(moduleId + "/" + bindingId, out var value))
				{
					return false;
				}
				binding = value.Binding;
				return true;
			}
		}

		public IReadOnlyList<KeybindingDescriptor> GetBindings()
		{
			lock (_sync)
			{
				List<KeybindingDescriptor> list = new List<KeybindingDescriptor>(_byQualifiedId.Count);
				foreach (BindingEntry value in _byQualifiedId.Values)
				{
					list.Add(value.Binding);
				}
				list.Sort((KeybindingDescriptor left, KeybindingDescriptor right) => StringComparer.Ordinal.Compare(left.QualifiedId, right.QualifiedId));
				return list.AsReadOnly();
			}
		}

		public IReadOnlyList<KeybindingConflict> GetConflicts()
		{
			lock (_sync)
			{
				List<KeybindingConflict> list = new List<KeybindingConflict>();
				foreach (string key in _byChord.Keys)
				{
					KeybindingConflict keybindingConflict = CreateConflictLocked(key);
					if (keybindingConflict != null)
					{
						list.Add(keybindingConflict);
					}
				}
				list.Sort((KeybindingConflict left, KeybindingConflict right) => StringComparer.Ordinal.Compare(left.Chord.CanonicalId, right.Chord.CanonicalId));
				return list.AsReadOnly();
			}
		}

		public IReadOnlyList<KeybindingConflict> GetConflictsFor(string moduleId)
		{
			RunicIdentifier.Require(moduleId, "moduleId");
			IReadOnlyList<KeybindingConflict> conflicts = GetConflicts();
			List<KeybindingConflict> list = new List<KeybindingConflict>();
			foreach (KeybindingConflict item in conflicts)
			{
				foreach (KeybindingDescriptor binding in item.Bindings)
				{
					if (string.Equals(binding.ModuleId, moduleId, StringComparison.Ordinal))
					{
						list.Add(item);
						break;
					}
				}
			}
			return list.AsReadOnly();
		}

		internal bool IsActive(string qualifiedId, long token)
		{
			lock (_sync)
			{
				BindingEntry value;
				return _byQualifiedId.TryGetValue(qualifiedId, out value) && value.Token == token;
			}
		}

		internal bool Unregister(string qualifiedId, long? requiredToken)
		{
			KeybindingDescriptor binding;
			KeybindingConflict conflict;
			lock (_sync)
			{
				if (!_byQualifiedId.TryGetValue(qualifiedId, out var entry) || (requiredToken.HasValue && entry.Token != requiredToken.Value))
				{
					return false;
				}
				binding = entry.Binding;
				_byQualifiedId.Remove(qualifiedId);
				List<BindingEntry> list = _byChord[binding.Chord.CanonicalId];
				list.RemoveAll((BindingEntry candidate) => candidate.Token == entry.Token);
				if (list.Count == 0)
				{
					_byChord.Remove(binding.Chord.CanonicalId);
				}
				conflict = CreateConflictLocked(binding.Chord.CanonicalId);
			}
			RaiseChanged(new KeybindingsChangedEventArgs(KeybindingChangeKind.Unregistered, binding, conflict));
			return true;
		}

		private KeybindingConflict CreateConflictLocked(string chordId)
		{
			if (!_byChord.TryGetValue(chordId, out var value) || value.Count < 2)
			{
				return null;
			}
			List<KeybindingDescriptor> list = new List<KeybindingDescriptor>(value.Count);
			foreach (BindingEntry item in value)
			{
				list.Add(item.Binding);
			}
			list.Sort((KeybindingDescriptor left, KeybindingDescriptor right) => StringComparer.Ordinal.Compare(left.QualifiedId, right.QualifiedId));
			return new KeybindingConflict(list[0].Chord, list.AsReadOnly());
		}

		private void RaiseChanged(KeybindingsChangedEventArgs arguments)
		{
			EventHandler<KeybindingsChangedEventArgs> eventHandler = this.Changed;
			if (eventHandler == null)
			{
				return;
			}
			Delegate[] invocationList = eventHandler.GetInvocationList();
			for (int i = 0; i < invocationList.Length; i++)
			{
				EventHandler<KeybindingsChangedEventArgs> eventHandler2 = (EventHandler<KeybindingsChangedEventArgs>)invocationList[i];
				try
				{
					eventHandler2(this, arguments);
				}
				catch (Exception)
				{
				}
			}
		}
	}
	public sealed class KeybindingRegistration : IDisposable
	{
		private readonly KeybindingConflictRegistry _registry;

		private readonly long _token;

		public KeybindingDescriptor Binding { get; }

		public bool IsActive => _registry.IsActive(Binding.QualifiedId, _token);

		internal KeybindingRegistration(KeybindingConflictRegistry registry, KeybindingDescriptor binding, long token)
		{
			_registry = registry;
			Binding = binding;
			_token = token;
		}

		public void Dispose()
		{
			_registry.Unregister(Binding.QualifiedId, _token);
		}
	}
}
namespace RunicStorage
{
	[BepInPlugin("chazman.RunicStorage", "Runic Storage", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "chazman.RunicStorage";

		public const string Name = "Runic Storage";

		public const string Version = "1.0.0";

		private readonly List<KeybindingRegistration> _keybindingRegistrations = new List<KeybindingRegistration>();

		private readonly KeybindingConflictRegistry _keybindings = new KeybindingConflictRegistry();

		private static readonly FieldInfo DragItemField = AccessTools.Field(typeof(InventoryGui), "m_dragItem");

		private static readonly FieldInfo CurrentContainerField = AccessTools.Field(typeof(InventoryGui), "m_currentContainer");

		private readonly Harmony _harmony = new Harmony("chazman.RunicStorage");

		private readonly StorageInputReader _inputReader = new StorageInputReader();

		private StorageActions _actions;

		private StorageSearchPanel _searchPanel;

		private bool _readyMessageShown;

		internal static ContainerIndex Index { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private static StorageSearchPanel ActiveSearchPanel { get; set; }

		internal static bool SearchPanelOpen => ActiveSearchPanel?.IsOpen ?? false;

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			PluginConfig.Bind(((BaseUnityPlugin)this).Config);
			try
			{
				((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged;
				ZInput.OnInputLayoutChanged += OnInputLayoutChanged;
				Localization.OnLanguageChange = (Action)Delegate.Combine(Localization.OnLanguageChange, new Action(ContainerHoverContents.InvalidateConfiguration));
				Index = new ContainerIndex();
				_searchPanel = new StorageSearchPanel();
				ActiveSearchPanel = _searchPanel;
				_actions = new StorageActions(Index, _searchPanel);
				RegisterKeybindings();
				_harmony.PatchAll(typeof(Plugin).Assembly);
				Index.RefreshLoadedContainers();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Storage v1.0.0 ready: chest hover, Quick Stack, Restock, Search, Sort, Store All, and Consolidate use native local ownership.");
				if (!ContainerHoverContents.IsSupported)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Chest-content hover is unavailable because its installed Container/PrivateArea adapter signatures did not match. Other Storage features remain enabled.");
				}
				LogConfigurationSummary("startup");
			}
			catch (Exception arg)
			{
				Shutdown();
				((BaseUnityPlugin)this).Logger.LogError((object)string.Format("{0} failed closed during startup: {1}", "Runic Storage", arg));
			}
		}

		private void Update()
		{
			if (_actions == null)
			{
				return;
			}
			try
			{
				ShowReadyMessage();
				_searchPanel?.Tick();
				StorageSearchPanel searchPanel = _searchPanel;
				if (searchPanel != null && searchPanel.IsOpen)
				{
					return;
				}
				StorageRouteContext context = CaptureRouteContext();
				StorageActionEdges num = _inputReader.ReadKeyboardEdges();
				StorageActionEdges storageActionEdges = _inputReader.ReadControllerEdges(context);
				StorageActionEdges edges = num | storageActionEdges;
				StorageActionRequest request = StorageActionRouter.Select(edges);
				if (!request.IsPresent)
				{
					return;
				}
				StorageRouteDecision storageRouteDecision = StorageActionRouter.Route(request, context);
				if (PluginConfig.DebugTransfers.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("input-detected edges=" + StorageInputReader.DescribeEdges(edges) + " selected=" + StorageActionDiagnostics.ActionCode(request.Action) + " source=" + StorageActionDiagnostics.OriginCode(request.Origin) + " route=" + storageRouteDecision.Outcome.ToString().ToLowerInvariant() + " reason=" + StorageActionDiagnostics.RouteReasonCode(storageRouteDecision.Reason)));
				}
				if (storageRouteDecision.Outcome != StorageRouteOutcome.Execute)
				{
					string text = StorageActionDiagnostics.RouteFeedback(storageRouteDecision.Reason);
					if (text.Length != 0)
					{
						Player localPlayer = Player.m_localPlayer;
						if ((Object)(object)localPlayer != (Object)null)
						{
							((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null);
						}
					}
					return;
				}
				switch (request.Action)
				{
				case StorageActionKind.QuickStack:
					_actions.QuickStack();
					break;
				case StorageActionKind.StoreAllOpenedContainer:
					_actions.StoreAllOpenedContainer();
					break;
				case StorageActionKind.Restock:
					_actions.Restock();
					break;
				case StorageActionKind.SortOpenedContainer:
					_actions.SortOpenedContainer();
					break;
				case StorageActionKind.Consolidate:
					_actions.ConsolidateCarriedStacks();
					break;
				case StorageActionKind.Search:
					_actions.Search();
					break;
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Runic Storage action stopped after restoring the in-flight item move; earlier completed moves, if any, remain applied: " + ex));
				Player localPlayer2 = Player.m_localPlayer;
				if ((Object)(object)localPlayer2 != (Object)null)
				{
					((Character)localPlayer2).Message((MessageType)2, "Runic Storage stopped safely; completed moves may remain. Check BepInEx/LogOutput.log.", 0, (Sprite)null);
				}
			}
		}

		private void OnGUI()
		{
			try
			{
				_searchPanel?.Draw();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Storage search list closed after a UI error: " + ex.Message));
				_searchPanel?.Close();
			}
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void RegisterKeybindings()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			DisposeKeybindings();
			RegisterKey("quick-stack", "Quick Stack", PluginConfig.QuickStackKey.Value);
			RegisterKey("restock", "Restock", PluginConfig.RestockKey.Value);
			RegisterKey("sort", "Sort Opened Container", PluginConfig.SortOpenedContainerKey.Value);
			RegisterKey("store-all", "Store All Into Opened Container", PluginConfig.StoreAllOpenedContainerKey.Value);
			RegisterKey("consolidate", "Consolidate Carried Stacks", PluginConfig.ConsolidateKey.Value);
			RegisterKey("search", "Search Nearby Storage", PluginConfig.SearchKey.Value);
			if (PluginConfig.ControllerShortcuts.Value)
			{
				RegisterControllerKey("controller-quick-stack", "Quick Stack (Controller)", PluginConfig.ControllerQuickStack.Value);
				RegisterControllerKey("controller-restock", "Restock (Controller)", PluginConfig.ControllerRestock.Value);
				RegisterControllerKey("controller-sort", "Sort Opened Container (Controller)", PluginConfig.ControllerSort.Value);
				RegisterControllerKey("controller-consolidate", "Consolidate (Controller)", PluginConfig.ControllerConsolidate.Value);
				RegisterControllerKey("controller-search", "Search (Controller)", PluginConfig.ControllerSearch.Value);
			}
		}

		private void RegisterKey(string bindingId, string displayName, KeyboardShortcut shortcut)
		{
			//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_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers)
			{
				list.Add(((object)modifier).ToString());
			}
			_keybindingRegistrations.Add(_keybindings.Register(new KeybindingDescriptor("runic.storage", bindingId, displayName, new InputChord("keyboard", ((object)((KeyboardShortcut)(ref shortcut)).MainKey).ToString(), list))));
		}

		private void RegisterControllerKey(string bindingId, string displayName, string action)
		{
			string text = (action ?? string.Empty).Trim();
			string text2 = (PluginConfig.ControllerModifier.Value ?? string.Empty).Trim();
			if (text.Length == 0 || text2.Length == 0 || string.Equals(text, text2, StringComparison.Ordinal))
			{
				return;
			}
			try
			{
				_keybindingRegistrations.Add(_keybindings.Register(new KeybindingDescriptor("runic.storage", bindingId, displayName, new InputChord("controller", text, new string[1] { text2 }))));
			}
			catch (ArgumentException ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Controller keybinding " + displayName + " was not registered: " + ex.Message));
			}
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs arguments)
		{
			ContainerHoverContents.InvalidateConfiguration();
			_inputReader.InvalidateControllerBindings();
			StorageControllerCollisionGuard.Reset();
			try
			{
				RegisterKeybindings();
				LogConfigurationSummary("configuration-changed");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Storage keybinding refresh failed: " + ex.Message));
			}
		}

		private void OnInputLayoutChanged()
		{
			_inputReader.InvalidateControllerBindings();
			StorageControllerCollisionGuard.Reset();
			ConfigEntry<bool> debugTransfers = PluginConfig.DebugTransfers;
			if (debugTransfers != null && debugTransfers.Value)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Controller input layout changed; Storage controller bindings will be re-resolved once.");
			}
		}

		private void DisposeKeybindings()
		{
			for (int num = _keybindingRegistrations.Count - 1; num >= 0; num--)
			{
				try
				{
					_keybindingRegistrations[num].Dispose();
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not unregister a storage keybinding cleanly: " + ex.Message));
				}
			}
			_keybindingRegistrations.Clear();
		}

		internal static StorageRouteContext CaptureRouteContext()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			//IL_002f: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			Player localPlayer = Player.m_localPlayer;
			bool flag = InventoryGui.IsVisible();
			bool flag2 = (Object)localPlayer != (Object)null && (Object)localPlayer == (Object)Player.m_localPlayer && ((Character)localPlayer).IsOwner();
			return new StorageRouteContext(PluginConfig.Enabled?.Value ?? false, (Object)localPlayer == (Object)null || InputBlocked(), HasDraggedItem(), flag, flag && HasOpenedContainer(), StoreGui.IsVisible(), Minimap.IsOpen(), flag2, flag2);
		}

		private static bool InputBlocked()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Expected O, but got Unknown
			if (Console.IsVisible() || Menu.IsVisible() || TextInput.IsVisible())
			{
				return true;
			}
			if ((Object)Chat.instance != (Object)null && Chat.instance.HasFocus())
			{
				return true;
			}
			if ((Object)TextViewer.instance != (Object)null && TextViewer.instance.IsVisible())
			{
				return true;
			}
			if (Hud.InRadial() || Hud.IsPieceSelectionVisible() || GameCamera.InFreeFly() || PlayerCustomizaton.IsBarberGuiVisible())
			{
				return true;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)localPlayer == (Object)null) && !((Character)localPlayer).IsDead() && !((Character)localPlayer).InCutscene())
			{
				return ((Character)localPlayer).IsTeleporting();
			}
			return true;
		}

		private static bool HasDraggedItem()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			if ((Object)InventoryGui.instance != (Object)null)
			{
				return DragItemField?.GetValue(InventoryGui.instance) is ItemData;
			}
			return false;
		}

		private static bool HasOpenedContainer()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			if ((Object)InventoryGui.instance != (Object)null)
			{
				return CurrentContainerField?.GetValue(InventoryGui.instance) is Container;
			}
			return false;
		}

		private void ShowReadyMessage()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			if (!_readyMessageShown && PluginConfig.Enabled.Value && PluginConfig.ShowReadyMessage.Value && !((Object)Player.m_localPlayer == (Object)null) && !((Object)MessageHud.instance == (Object)null))
			{
				_readyMessageShown = true;
				string text = (PluginConfig.ControllerShortcuts.Value ? (" Controller: hold " + PluginConfig.ControllerModifier.Value + "; see config for routes.") : string.Empty);
				((Character)Player.m_localPlayer).Message((MessageType)1, "Runic Storage ready — " + ShortcutLabel(PluginConfig.QuickStackKey.Value) + " Quick Stack; " + ShortcutLabel(PluginConfig.RestockKey.Value) + " Restock; " + ShortcutLabel(PluginConfig.SearchKey.Value) + " Search; " + ShortcutLabel(PluginConfig.ConsolidateKey.Value) + " Consolidate; open a chest and use " + ShortcutLabel(PluginConfig.SortOpenedContainerKey.Value) + " to Sort or " + ShortcutLabel(PluginConfig.StoreAllOpenedContainerKey.Value) + " to Store All." + text, 0, (Sprite)null);
			}
		}

		private void LogConfigurationSummary(string reason)
		{
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_0245: Unknown result type (might be due to invalid IL or missing references)
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			string text = (PluginConfig.ControllerShortcuts.Value ? ("hold " + PluginConfig.ControllerModifier.Value + ": quick=" + PluginConfig.ControllerQuickStack.Value + ", restock=" + PluginConfig.ControllerRestock.Value + ", sort=" + PluginConfig.ControllerSort.Value + ", consolidate=" + PluginConfig.ControllerConsolidate.Value + ", search=" + PluginConfig.ControllerSearch.Value) : "disabled");
			((BaseUnityPlugin)this).Logger.LogInfo((object)($"Storage configuration ({reason}): Enabled={PluginConfig.Enabled.Value}; " + $"Range={PluginConfig.RangeMeters.Value:0.##}m; MaxCandidates={PluginConfig.MaximumCandidates.Value}; " + $"ProtectHotbar={PluginConfig.ProtectHotbar.Value}; DebugTransfers={PluginConfig.DebugTransfers.Value}; " + $"Hover=[enabled={PluginConfig.ShowContentsOnHover.Value}, kinds={PluginConfig.HoverMaximumItemKinds.Value}, " + $"perLine={PluginConfig.HoverItemsPerLine.Value}, characters={PluginConfig.HoverMaximumCharacters.Value}, " + $"stacks={PluginConfig.HoverMaximumStacksExamined.Value}, " + $"snapshotCharacters={PluginConfig.HoverMaximumSnapshotCharacters.Value}, " + string.Format("retry={0:0.##}s, signatures={1}]; ", PluginConfig.HoverRefreshIntervalSeconds.Value, ContainerHoverContents.IsSupported ? "ready" : "unavailable") + "keyboard=[quick " + ShortcutLabel(PluginConfig.QuickStackKey.Value) + ", restock " + ShortcutLabel(PluginConfig.RestockKey.Value) + ", sort " + ShortcutLabel(PluginConfig.SortOpenedContainerKey.Value) + ", store-all " + ShortcutLabel(PluginConfig.StoreAllOpenedContainerKey.Value) + ", consolidate " + ShortcutLabel(PluginConfig.ConsolidateKey.Value) + ", search " + ShortcutLabel(PluginConfig.SearchKey.Value) + "]; controller=[" + text + "]; controller-validation=" + _inputReader.ControllerStatusSummary() + "."));
		}

		private static string ShortcutLabel(KeyboardShortcut shortcut)
		{
			//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_001d: 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)
			List<string> list = new List<string>();
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers)
			{
				list.Add(((object)modifier).ToString());
			}
			list.Add(((object)((KeyboardShortcut)(ref shortcut)).MainKey).ToString());
			return string.Join("+", list);
		}

		private void Shutdown()
		{
			((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged;
			ZInput.OnInputLayoutChanged -= OnInputLayoutChanged;
			Localization.OnLanguageChange = (Action)Delegate.Remove(Localization.OnLanguageChange, new Action(ContainerHoverContents.InvalidateConfiguration));
			_inputReader.InvalidateControllerBindings();
			StorageControllerCollisionGuard.Reset();
			StorageSearchGameplayInputGuard.Reset();
			ContainerHoverContents.Reset();
			try
			{
				_harmony.UnpatchSelf();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not unpatch Runic Storage cleanly: " + ex.Message));
			}
			DisposeKeybindings();
			_actions = null;
			_searchPanel?.Dispose();
			_searchPanel = null;
			ActiveSearchPanel = null;
			Index = null;
			Log = null;
		}
	}
	internal static class PluginConfig
	{
		internal static ConfigEntry<bool> Enabled { get; private set; }

		internal static ConfigEntry<bool> ShowReadyMessage { get; private set; }

		internal static ConfigEntry<float> RangeMeters { get; private set; }

		internal static ConfigEntry<int> MaximumCandidates { get; private set; }

		internal static ConfigEntry<bool> ProtectHotbar { get; private set; }

		internal static ConfigEntry<bool> ShowContentsOnHover { get; private set; }

		internal static ConfigEntry<int> HoverMaximumItemKinds { get; private set; }

		internal static ConfigEntry<int> HoverItemsPerLine { get; private set; }

		internal static ConfigEntry<int> HoverMaximumCharacters { get; private set; }

		internal static ConfigEntry<int> HoverMaximumStacksExamined { get; private set; }

		internal static ConfigEntry<int> HoverMaximumSnapshotCharacters { get; private set; }

		internal static ConfigEntry<float> HoverRefreshIntervalSeconds { get; private set; }

		internal static ConfigEntry<KeyboardShortcut> QuickStackKey { get; private set; }

		internal static ConfigEntry<KeyboardShortcut> RestockKey { get; private set; }

		internal static ConfigEntry<string> RestockTargets { get; private set; }

		internal static ConfigEntry<KeyboardShortcut> SortOpenedContainerKey { get; private set; }

		internal static ConfigEntry<KeyboardShortcut> StoreAllOpenedContainerKey { get; private set; }

		internal static ConfigEntry<string> LockedContainerSlots { get; private set; }

		internal static ConfigEntry<KeyboardShortcut> ConsolidateKey { get; private set; }

		internal static ConfigEntry<KeyboardShortcut> SearchKey { get; private set; }

		internal static ConfigEntry<string> SearchItem { get; private set; }

		internal static ConfigEntry<int> SearchMenuFontSize { get; private set; }

		internal static ConfigEntry<StorageSearchMenuFontColor> SearchMenuFontColor { get; private set; }

		internal static ConfigEntry<bool> ControllerShortcuts { get; private set; }

		internal static ConfigEntry<string> ControllerModifier { get; private set; }

		internal static ConfigEntry<string> ControllerQuickStack { get; private set; }

		internal static ConfigEntry<string> ControllerRestock { get; private set; }

		internal static ConfigEntry<string> ControllerSort { get; private set; }

		internal static ConfigEntry<string> ControllerConsolidate { get; private set; }

		internal static ConfigEntry<string> ControllerSearch { get; private set; }

		internal static ConfigEntry<bool> DebugTransfers { get; private set; }

		internal static void Bind(ConfigFile config)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Expected O, but got Unknown
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Expected O, but got Unknown
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Expected O, but got Unknown
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Expected O, but got Unknown
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Expected O, but got Unknown
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: Expected O, but got Unknown
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Expected O, but got Unknown
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0252: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_031d: Unknown result type (might be due to invalid IL or missing references)
			//IL_034c: Unknown result type (might be due to invalid IL or missing references)
			//IL_039f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a9: Expected O, but got Unknown
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable Runic Storage gameplay actions.");
			ShowReadyMessage = config.Bind<bool>("General", "ShowReadyMessage", true, "Show a one-time in-world control reminder after the local player loads.");
			RangeMeters = config.Bind<float>("Discovery", "RangeMeters", 20f, new ConfigDescription("Nearby storage radius. Hard limited to 50 meters.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f), Array.Empty<object>()));
			MaximumCandidates = config.Bind<int>("Discovery", "MaximumCandidates", 64, new ConfigDescription("Maximum cached containers examined per action.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 256), Array.Empty<object>()));
			ProtectHotbar = config.Bind<bool>("Safety", "ProtectHotbar", true, "Never quick-stack, store-all, or consolidate items in the top-row hotbar.");
			ShowContentsOnHover = config.Bind<bool>("Hover", "ShowContents", true, "List a closed authorized container's synchronized contents in its hover text. Private, busy, unsynchronized, or Runic-reserved containers retain vanilla text only.");
			HoverMaximumItemKinds = config.Bind<int>("Hover", "MaximumItemKinds", 8, new ConfigDescription("Maximum distinct item kinds shown in one hover summary.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 24), Array.Empty<object>()));
			HoverItemsPerLine = config.Bind<int>("Hover", "ItemsPerLine", 3, new ConfigDescription("Maximum item kinds placed on each summary line.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 4), Array.Empty<object>()));
			HoverMaximumCharacters = config.Bind<int>("Hover", "MaximumCharacters", 320, new ConfigDescription("Hard character ceiling for the complete generated contents suffix, including formatting tags.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(64, 1024), Array.Empty<object>()));
			HoverMaximumStacksExamined = config.Bind<int>("Hover", "MaximumStacksExamined", 256, new ConfigDescription("Maximum inventory stacks examined when rebuilding one changed summary. Additional stacks are reported as unscanned.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(16, 1024), Array.Empty<object>()));
			HoverMaximumSnapshotCharacters = config.Bind<int>("Hover", "MaximumSnapshotCharacters", 262144, new ConfigDescription("Maximum persisted Base64 inventory characters accepted for an exact hover snapshot. Oversized third-party containers retain vanilla text.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(16384, 1048576), Array.Empty<object>()));
			HoverRefreshIntervalSeconds = config.Bind<float>("Hover", "FailedRefreshRetrySeconds", 0.5f, new ConfigDescription("Retry delay after a container cannot prove an exact synchronized inventory snapshot. Stable summaries remain cached by exact persisted item-payload evidence.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), Array.Empty<object>()));
			QuickStackKey = config.Bind<KeyboardShortcut>("Keys", "QuickStack", new KeyboardShortcut((KeyCode)113, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Deposit eligible carried stacks into authorized nearby containers already holding that item.");
			RestockKey = config.Bind<KeyboardShortcut>("Keys", "Restock", new KeyboardShortcut((KeyCode)114, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Restock configured carried targets from authorized nearby storage.");
			RestockTargets = config.Bind<string>("Restock", "Targets", "Wood=50,Stone=50", "Comma-separated prefab/name targets, for example Wood=50,Stone=50.");
			SortOpenedContainerKey = config.Bind<KeyboardShortcut>("Keys", "SortOpenedContainer", new KeyboardShortcut((KeyCode)115, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Sort the currently opened authorized container by category, name, quality, then weight.");
			StoreAllOpenedContainerKey = config.Bind<KeyboardShortcut>("Keys", "StoreAllOpenedContainer", new KeyboardShortcut((KeyCode)97, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Store every eligible carried item in the currently opened authorized locally owned container.");
			LockedContainerSlots = config.Bind<string>("Sort", "LockedSlots", string.Empty, "Semicolon-separated zero-based slots to leave fixed, for example 0,0;1,0.");
			ConsolidateKey = config.Bind<KeyboardShortcut>("Keys", "ConsolidateCarriedStacks", new KeyboardShortcut((KeyCode)99, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Safely consolidate compatible carried stacks while respecting protected slots and equipment.");
			SearchKey = config.Bind<KeyboardShortcut>("Keys", "Search", new KeyboardShortcut((KeyCode)102, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Open a selectable list of item kinds in authorized nearby containers and highlight every matching chest.");
			SearchItem = config.Bind<string>("Search", "SearchItem", "Wood", "Legacy setting retained for configuration compatibility; Alt+F now opens the complete nearby-item list.");
			SearchMenuFontSize = config.Bind<int>("Search", "MenuFontSize", 14, new ConfigDescription("Font size for every title, label, text field, and button in the Alt+F nearby-item window.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 32), Array.Empty<object>()));
			StorageSearchMenuFontColor migrated;
			bool num = TryReadAndRemoveLegacySearchMenuFontColor(config, out migrated);
			SearchMenuFontColor = config.Bind<StorageSearchMenuFontColor>("Search", "MenuFontColor", StorageSearchMenuFontColor.LightGray, "Named font color for the complete Alt+F nearby-item window. Configuration Manager presents the available colors as a dropdown.");
			if (num)
			{
				SearchMenuFontColor.Value = migrated;
			}
			ControllerShortcuts = config.Bind<bool>("Controller", "Enabled", true, "Enable controller chords resolved through Valheim's current ZInput action map.");
			ControllerModifier = config.Bind<string>("Controller", "ModifierAction", "JoyAltKeys", "Valheim ZInput action held as the controller modifier. Change only to an existing action name.");
			ControllerQuickStack = config.Bind<string>("Controller", "QuickStackAction", "JoyDPadDown", "Valheim ZInput action pressed with ModifierAction to Quick Stack.");
			ControllerRestock = config.Bind<string>("Controller", "RestockAction", "JoyDPadUp", "Valheim ZInput action pressed with ModifierAction to Restock.");
			ControllerSort = config.Bind<string>("Controller", "SortOpenedContainerAction", "JoyRStick", "Valheim ZInput action pressed with ModifierAction to sort the opened container.");
			ControllerConsolidate = config.Bind<string>("Controller", "ConsolidateAction", "JoyDPadLeft", "Valheim ZInput action pressed with ModifierAction to consolidate carried stacks.");
			ControllerSearch = config.Bind<string>("Controller", "SearchAction", "JoyDPadRight", "Valheim ZInput action pressed with ModifierAction to search nearby storage.");
			DebugTransfers = config.Bind<bool>("Diagnostics", "DebugTransfers", false, "Log detected controls, routing decisions, discovery counts, action summaries, transfer legs, and stable no-op/denial codes.");
		}

		private static bool TryReadAndRemoveLegacySearchMenuFontColor(ConfigFile config, out StorageSearchMenuFontColor migrated)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			ConfigDefinition val = new ConfigDefinition("Search", "MenuFontColor");
			bool result = HasOrphanedValue(config, val);
			ConfigEntry<string> val2 = config.Bind<string>(val, StorageSearchMenuFontColor.LightGray.ToString(), new ConfigDescription("Legacy Alt+F menu color migration entry.", (AcceptableValueBase)null, Array.Empty<object>()));
			migrated = StorageSearchMenuAppearance.NormalizeFontColorConfigValue(val2.Value);
			if (!config.Remove(val))
			{
				throw new InvalidOperationException("Runic Storage could not replace its legacy MenuFontColor setting.");
			}
			return result;
		}

		private static bool HasOrphanedValue(ConfigFile config, ConfigDefinition definition)
		{
			object obj = typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(config, null);
			if (obj == null)
			{
				obj = typeof(ConfigFile).GetField("<OrphanedEntries>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(config);
			}
			if (obj is IDictionary<ConfigDefinition, string> dictionary)
			{
				return dictionary.ContainsKey(definition);
			}
			return false;
		}
	}
}
namespace RunicStorage.Runtime
{
	internal static class ContainerHoverContents
	{
		private delegate bool CheckAccessDelegate(Container container, long playerId);

		private delegate string TranslateDelegate(Localization localization, string key);

		private delegate bool WardStateDelegate(PrivateArea area);

		private delegate bool WardContainsDelegate(PrivateArea area, Vector3 point, float radius);

		private sealed class CacheEntry
		{
			private WeakReference<string> _persistedReference;

			internal HoverPersistedEvidence Evidence;

			internal bool HasEvidence;

			internal long ConfigurationGeneration;

			internal float NextRetryAt;

			internal bool Verified;

			internal string Suffix;

			internal string BaseHoverText;

			internal string CombinedHoverText;

			internal long LastAccess;

			internal bool TryGetPersistedReference(out string persisted)
			{
				persisted = null;
				if (_persistedReference != null)
				{
					return _persistedReference.TryGetTarget(out persisted);
				}
				return false;
			}

			internal void SetPersistedReference(string persisted)
			{
				if (persisted == null)
				{
					_persistedReference = null;
				}
				else if (_persistedReference == null)
				{
					_persistedReference = new WeakReference<string>(persisted);
				}
				else
				{
					_persistedReference.SetTarget(persisted);
				}
			}
		}

		private const int MaximumCacheEntries = 512;

		private const int MaximumWardAreasExamined = 4096;

		private static readonly CheckAccessDelegate CheckAccess = ResolveCheckAccess();

		private static readonly TranslateDelegate Translate = ResolveTranslate();

		private static readonly FieldRef<Container, bool> Loading = ResolveLoading();

		private static readonly List<PrivateArea> WardAreas = ResolveWardAreas();

		private static readonly WardStateDelegate WardEnabled = ResolveWardState("IsEnabled");

		private static readonly WardStateDelegate WardLocalAccess = ResolveWardState("HaveLocalAccess");

		private static readonly WardContainsDelegate WardContains = ResolveWardContains();

		private static readonly Dictionary<Container, CacheEntry> Cache = new Dictionary<Container, CacheEntry>();

		private static long _configurationGeneration = 1L;

		private static long _accessSequence;

		internal static bool IsSupported
		{
			get
			{
				if (CheckAccess != null && Translate != null && Loading != null && WardAreas != null && WardEnabled != null && WardLocalAccess != null)
				{
					return WardContains != null;
				}
				return false;
			}
		}

		internal static void Append(Container container, ref string hoverText)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected O, but got Unknown
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			if (!IsSupported || (Object)container == (Object)null || !HoverBaseTextPolicy.Allows(hoverText))
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			ConfigEntry<bool> enabled = PluginConfig.Enabled;
			bool flag = enabled != null && enabled.Value && (PluginConfig.ShowContentsOnHover?.Value ?? false);
			if (!flag || (Object)localPlayer == (Object)null || !((Behaviour)container).isActiveAndEnabled || (int)container.m_privacy == 0 || IsBusy(container, null))
			{
				return;
			}
			ZNetView val = ValheimContainerIdentity.NetworkView(container);
			ZDO val2 = (((Object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
			if (val2 == null || Loading.Invoke(container) || IsBusy(container, val2))
			{
				return;
			}
			Vector3 position = ((Component)container).transform.position;
			Vector3 val3 = position - ((Component)localPlayer).transform.position;
			bool flag2 = HoverRangePolicy.IsWithinPhysicalReach(((Vector3)(ref val3)).sqrMagnitude, localPlayer.m_maxInteractDistance);
			if (!flag2)
			{
				return;
			}
			bool flag3 = !container.m_checkGuardStone || HasStrictWardAccess(position);
			if (!flag3)
			{
				return;
			}
			long playerID = localPlayer.GetPlayerID();
			bool flag4;
			try
			{
				flag4 = CheckAccess(container, playerID);
			}
			catch
			{
				return;
			}
			if (!flag4)
			{
				return;
			}
			bool durableClaimAbsent = true;
			if (!HoverDisclosurePolicy.AllowsBeforeSynchronization(new HoverDisclosureFacts(flag, localPlayerAvailable: true, containerActive: true, networkObjectValid: true, nonPrivate: true, flag3, flag4, flag2, closed: true, mutationIdle: true, durableClaimAbsent, synchronized: false)))
			{
				return;
			}
			CacheEntry orCreate = GetOrCreate(container);
			orCreate.LastAccess = ++_accessSequence;
			string text = val2.GetString(ZDOVars.s_items, string.Empty);
			bool samePersistedEvidence;
			HoverPersistedEvidence expectedEvidence = CaptureEvidence(orCreate, text, out samePersistedEvidence);
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			switch (HoverCachePolicy.Decide(orCreate.Verified, samePersistedEvidence, orCreate.ConfigurationGeneration, _configurationGeneration, orCreate.NextRetryAt, realtimeSinceStartup))
			{
			case HoverCacheDecision.SuppressUntilRetry:
				return;
			case HoverCacheDecision.Refresh:
				if (!TryRefresh(container, val, val2, text, expectedEvidence, realtimeSinceStartup, orCreate, localPlayer, playerID))
				{
					return;
				}
				break;
			}
			if (HoverDisclosurePolicy.Allows(new HoverDisclosureFacts(flag, localPlayerAvailable: true, containerActive: true, networkObjectValid: true, nonPrivate: true, flag3, flag4, flag2, closed: true, mutationIdle: true, durableClaimAbsent, orCreate.Verified)) && !string.IsNullOrEmpty(orCreate.Suffix))
			{
				if (!string.Equals(orCreate.BaseHoverText, hoverText, StringComparison.Ordinal))
				{
					orCreate.BaseHoverText = hoverText;
					orCreate.CombinedHoverText = hoverText + orCreate.Suffix;
				}
				hoverText = orCreate.CombinedHoverText;
			}
		}

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

		internal static void InvalidateConfiguration()
		{
			_configurationGeneration++;
			if (_configurationGeneration == 0L)
			{
				_configurationGeneration = 1L;
			}
		}

		internal static void Reset()
		{
			Cache.Clear();
			_configurationGeneration = 1L;
			_accessSequence = 0L;
		}

		internal static bool TryGetSynchronizedReadSnapshot(Container container, out Inventory inventory, out long revision)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Expected O, but got Unknown
			inventory = null;
			revision = 0L;
			if ((Object)container == (Object)null || Loading == null)
			{
				return false;
			}
			try
			{
				ZNetView val = ValheimContainerIdentity.NetworkView(container);
				ZDO val2 = (((Object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
				if (val2 == null || Loading.Invoke(container) || IsBusy(container, val2))
				{
					return false;
				}
				string text = val2.GetString(ZDOVars.s_items, string.Empty);
				HoverPersistedEvidence evidence = HoverPersistedEvidence.Capture(text, PluginConfig.HoverMaximumSnapshotCharacters.Value);
				if (!TryGetExactInventory(container, text, evidence, out var inventory2))
				{
					return false;
				}
				ZNetView val3 = ValheimContainerIdentity.NetworkView(container);
				ZDO val4 = (((Object)val3 != (Object)null && val3.IsValid()) ? val3.GetZDO() : null);
				string a = ((val4 != null) ? val4.GetString(ZDOVars.s_items, string.Empty) : null);
				if ((Object)(object)val3 != (Object)(object)val || val4 != val2 || val4 == null || Loading.Invoke(container) || IsBusy(container, val4) || !string.Equals(a, text, StringComparison.Ordinal))
				{
					return false;
				}
				inventory = inventory2;
				revision = val4.DataRevision;
				return true;
			}
			catch
			{
				inventory = null;
				revision = 0L;
				return false;
			}
		}

		private static bool TryRefresh(Container container, ZNetView expectedView, ZDO expectedZdo, string expectedPersistedItems, HoverPersistedEvidence expectedEvidence, float now, CacheEntry entry, Player expectedPlayer, long expectedPlayerId)
		{
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Expected O, but got Unknown
			//IL_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Expected O, but got Unknown
			//IL_025a: Expected O, but got Unknown
			//IL_0261: Unknown result type (might be due to invalid IL or missing references)
			//IL_026c: Expected O, but got Unknown
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e1: Unknown result type (might be due to invalid IL or missing references)
			entry.Verified = false;
			entry.Evidence = expectedEvidence;
			entry.HasEvidence = true;
			entry.SetPersistedReference(expectedEvidence.IsAdmissible ? expectedPersistedItems : null);
			entry.ConfigurationGeneration = _configurationGeneration;
			entry.NextRetryAt = now + Mathf.Clamp(PluginConfig.HoverRefreshIntervalSeconds.Value, 0.1f, 5f);
			entry.Suffix = string.Empty;
			entry.BaseHoverText = null;
			entry.CombinedHoverText = null;
			try
			{
				if (!expectedEvidence.IsAdmissible || !TryGetExactInventory(container, expectedPersistedItems, expectedEvidence, out var inventory))
				{
					return false;
				}
				List<ItemData> allItems = inventory.GetAllItems();
				int num = Math.Min(allItems.Count, Mathf.Clamp(PluginConfig.HoverMaximumStacksExamined.Value, 16, 1024));
				int num2 = allItems.Count - num;
				List<HoverContentEntry> list = new List<HoverContentEntry>(num);
				for (int i = 0; i < num; i++)
				{
					ItemData val = allItems[i];
					if (val != null && val.m_stack > 0)
					{
						string text = ValheimContainerIdentity.ResourceId(val);
						if (text.Length == 0 || text.Length > 256)
						{
							num2++;
						}
						else
						{
							list.Add(new HoverContentEntry(text, DisplayName(val, text), val.m_stack));
						}
					}
				}
				string suffix = ContainerHoverSummaryFormatter.Format(list, Mathf.Clamp(PluginConfig.HoverMaximumItemKinds.Value, 1, 24), Mathf.Clamp(PluginConfig.HoverItemsPerLine.Value, 1, 4), Mathf.Clamp(PluginConfig.HoverMaximumCharacters.Value, 64, 1024), num2);
				ZNetView val2 = ValheimContainerIdentity.NetworkView(container);
				ZDO val3 = (((Object)val2 != (Object)null && val2.IsValid()) ? val2.GetZDO() : null);
				string text2 = ((val3 != null) ? val3.GetString(ZDOVars.s_items, string.Empty) : null);
				HoverPersistedEvidence hoverPersistedEvidence = HoverPersistedEvidence.Capture(text2, PluginConfig.HoverMaximumSnapshotCharacters.Value);
				if ((Object)(object)val2 == (Object)(object)expectedView && val3 == expectedZdo && val3 != null && hoverPersistedEvidence.Equals(expectedEvidence) && string.Equals(text2, expectedPersistedItems, StringComparison.Ordinal))
				{
					ConfigEntry<bool> enabled = PluginConfig.Enabled;
					if (enabled != null && enabled.Value)
					{
						ConfigEntry<bool> showContentsOnHover = PluginConfig.ShowContentsOnHover;
						if (showContentsOnHover != null && showContentsOnHover.Value && !((Object)Player.m_localPlayer != (Object)expectedPlayer) && !((Object)expectedPlayer == (Object)null) && ((Behaviour)container).isActiveAndEnabled && (int)container.m_privacy != 0 && !Loading.Invoke(container) && !IsBusy(container, val3))
						{
							Vector3 position = ((Component)container).transform.position;
							Vector3 val4 = position - ((Component)expectedPlayer).transform.position;
							if (!HoverRangePolicy.IsWithinPhysicalReach(((Vector3)(ref val4)).sqrMagnitude, expectedPlayer.m_maxInteractDistance) || (container.m_checkGuardStone && !HasStrictWardAccess(position)) || !CheckAccess(container, expectedPlayerId))
							{
								return false;
							}
							entry.Suffix = suffix;
							entry.Verified = true;
							entry.NextRetryAt = 0f;
							return true;
						}
					}
				}
				return false;
			}
			catch
			{
				return false;
			}
		}

		private static bool TryGetExactInventory(Container container, string persisted, HoverPersistedEvidence evidence, out Inventory inventory)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Expected O, but got Unknown
			inventory = null;
			if ((Object)container == (Object)null || !evidence.IsAdmissible || Loading.Invoke(container))
			{
				return false;
			}
			inventory = container.GetInventory();
			if (inventory == null)
			{
				return false;
			}
			List<ItemData> allItems = inventory.GetAllItems();
			if (string.IsNullOrEmpty(persisted))
			{
				return allItems.Count == 0;
			}
			if (!HoverSnapshotBounds.AllowsEnvelope(allItems.Count, persisted.Length, PluginConfig.HoverMaximumSnapshotCharacters.Value))
			{
				return false;
			}
			int maximumSerializedBytes = HoverSnapshotBounds.SerializedByteCeiling(PluginConfig.HoverMaximumSnapshotCharacters.Value);
			if (!HasBoundedSerializedShape(allItems, maximumSerializedBytes))
			{
				return false;
			}
			ZPackage val = new ZPackage();
			inventory.Save(val);
			if (!HoverSnapshotBounds.MatchesExactSerializedSize(val.Size(), persisted.Length, PluginConfig.HoverMaximumSnapshotCharacters.Value))
			{
				return false;
			}
			return string.Equals(val.GetBase64(), persisted, StringComparison.Ordinal);
		}

		private static bool IsBusy(Container container, ZDO zdo)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			if ((Object)container == (Object)null || container.IsInUse() || ((Object)container.m_wagon != (Object)null && container.m_wagon.InUse()))
			{
				return true;
			}
			if (zdo != null)
			{
				return zdo.GetBool(ZDOVars.s_inUse, false);
			}
			return false;
		}

		private static bool HasBoundedSerializedShape(List<ItemData> items, int maximumSerializedBytes)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			if (items == null || items.Count > 1024 || maximumSerializedBytes <= 0)
			{
				return false;
			}
			long nextBytes = 8L;
			int num = 0;
			for (int i = 0; i < items.Count; i++)
			{
				ItemData val = items[i];
				if (val == null || (Object)val.m_dropPrefab == (Object)null)
				{
					return false;
				}
				nextBytes += 64;
				if (!HoverSnapshotBounds.TryAddStringEstimate(nextBytes, ((Object)val.m_dropPrefab).name?.Length ?? 0, maximumSerializedBytes, out nextBytes) || !HoverSnapshotBounds.TryAddStringEstimate(nextBytes, val.m_crafterName?.Length ?? 0, maximumSerializedBytes, out nextBytes))
				{
					return false;
				}
				Dictionary<string, string> customData = val.m_customData;
				int num2 = customData?.Count ?? 0;
				if (!HoverSnapshotBounds.AllowsCustomDataAddition(num, num2))
				{
					return false;
				}
				num += num2;
				if (customData == null)
				{
					continue;
				}
				foreach (KeyValuePair<string, string> item in customData)
				{
					if (!HoverSnapshotBounds.TryAddStringEstimate(nextBytes, item.Key?.Length ?? 0, maximumSerializedBytes, out nextBytes) || !HoverSnapshotBounds.TryAddStringEstimate(nextBytes, item.Value?.Length ?? 0, maximumSerializedBytes, out nextBytes))
					{
						return false;
					}
				}
			}
			return nextBytes <= maximumSerializedBytes;
		}

		private static bool HasStrictWardAccess(Vector3 position)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				int count = WardAreas.Count;
				if (count > 4096)
				{
					return false;
				}
				for (int i = 0; i < count; i++)
				{
					PrivateArea val = WardAreas[i];
					if (!((Object)val == (Object)null))
					{
						bool num = WardEnabled(val);
						bool flag = num && WardContains(val, position, 0f);
						bool localAccess = !flag || WardLocalAccess(val);
						if (StrictWardDisclosurePolicy.IsHostileOverlap(num, flag, localAccess))
						{
							return false;
						}
					}
				}
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static string DisplayName(ItemData item, string fallback)
		{
			if (!HoverLabelPolicy.TryPrepareLocalizationToken(item?.m_shared?.m_name, out var token))
			{
				return HoverLabelPolicy.NormalizeDisplayLabel(fallback, "Item");
			}
			try
			{
				Localization instance = Localization.instance;
				string key;
				return HoverLabelPolicy.NormalizeDisplayLabel((instance != null && HoverLabelPolicy.TryGetTranslationKey(token, out key)) ? Translate(instance, key) : HoverLabelPolicy.WithoutLocalizationMarker(token), fallback);
			}
			catch
			{
				return HoverLabelPolicy.NormalizeDisplayLabel(HoverLabelPolicy.WithoutLocalizationMarker(token), fallback);
			}
		}

		private static HoverPersistedEvidence CaptureEvidence(CacheEntry entry, string persisted, out bool samePersistedEvidence)
		{
			if (persisted == null)
			{
				persisted = string.Empty;
			}
			string persisted2 = null;
			if (entry.HasEvidence && entry.TryGetPersistedReference(out persisted2) && (object)persisted2 == persisted)
			{
				samePersistedEvidence = true;
				return entry.Evidence;
			}
			HoverPersistedEvidence hoverPersistedEvidence = HoverPersistedEvidence.Capture(persisted, PluginConfig.HoverMaximumSnapshotCharacters.Value);
			samePersistedEvidence = entry.HasEvidence && entry.Evidence.Equals(hoverPersistedEvidence);
			if (samePersistedEvidence && hoverPersistedEvidence.IsAdmissible)
			{
				samePersistedEvidence = persisted2 != null && string.Equals(persisted2, persisted, StringComparison.Ordinal);
			}
			if (samePersistedEvidence && hoverPersistedEvidence.IsAdmissible)
			{
				entry.SetPersistedReference(persisted);
			}
			return hoverPersistedEvidence;
		}

		private static CacheEntry GetOrCreate(Container container)
		{
			if (Cache.TryGetValue(container, out var value))
			{
				return value;
			}
			if (Cache.Count >= 512)
			{
				EvictOldest();
			}
			value = new CacheEntry();
			Cache.Add(container, value);
			return value;
		}

		private static void EvictOldest()
		{
			Container val = null;
			long num = long.MaxValue;
			foreach (KeyValuePair<Container, CacheEntry> item in Cache)
			{
				if (item.Value.LastAccess < num)
				{
					val = item.Key;
					num = item.Value.LastAccess;
				}
			}
			if ((Object)(object)val != (Object)null)
			{
				Cache.Remove(val);
			}
		}

		private static CheckAccessDelegate ResolveCheckAccess()
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null);
				return (methodInfo == null) ? null : AccessTools.MethodDelegate<CheckAccessDelegate>(methodInfo, (object)null, true);
			}
			catch
			{
				return null;
			}
		}

		private static TranslateDelegate ResolveTranslate()
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Localization), "Translate", new Type[1] { typeof(string) }, (Type[])null);
				return (methodInfo == null || methodInfo.IsStatic || methodInfo.ReturnType != typeof(string)) ? null : AccessTools.MethodDelegate<TranslateDelegate>(methodInfo, (object)null, true);
			}
			catch
			{
				return null;
			}
		}

		private static FieldRef<Container, bool> ResolveLoading()
		{
			try
			{
				return AccessTools.FieldRefAccess<Container, bool>("m_loading");
			}
			catch
			{
				return null;
			}
		}

		private static List<PrivateArea> ResolveWardAreas()
		{
			try
			{
				return AccessTools.Field(typeof(PrivateArea), "m_allAreas")?.GetValue(null) as List<PrivateArea>;
			}
			catch
			{
				return null;
			}
		}

		private static WardStateDelegate ResolveWardState(string methodName)
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(PrivateArea), methodName, Type.EmptyTypes, (Type[])null);
				return (methodInfo == null) ? null : AccessTools.MethodDelegate<WardStateDelegate>(methodInfo, (object)null, true);
			}
			catch
			{
				return null;
			}
		}

		private static WardContainsDelegate ResolveWardContains()
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(PrivateArea), "IsInside", new Type[2]
				{
					typeof(Vector3),
					typeof(float)
				}, (Type[])null);
				return (methodInfo == null) ? null : AccessTools.MethodDelegate<WardContainsDelegate>(methodInfo, (object)null, true);
			}
			catch
			{
				return null;
			}
		}
	}
	internal sealed class ContainerIndex
	{
		private readonly struct CellKey : IEquatable<CellKey>
		{
			internal int X { get; }

			internal int Z { get; }

			internal CellKey(int x, int z)
			{
				X = x;
				Z = z;
			}

			internal static CellKey From(Vector3 value)
			{
				//IL_0000: 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)
				return new CellKey(ContainerSpatialPolicy.CellCoordinate(value.x), ContainerSpatialPolicy.CellCoordinate(value.z));
			}

			public bool Equals(CellKey other)
			{
				if (X == other.X)
				{
					return Z == other.Z;
				}
				return false;
			}

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

			public override int GetHashCode()
			{
				return (X * 397) ^ Z;
			}
		}

		private readonly struct Entry
		{
			internal int InstanceId { get; }

			internal Container Container { get; }

			internal Entry(int instanceId, Container container)
			{
				InstanceId = instanceId;
				Container = container;
			}
		}

		private readonly struct Membership
		{
			internal CellKey Cell { get; }

			internal string EndpointId { get; }

			internal Container Container { get; }

			internal Membership(CellKey cell, string endpointId, Container container)
			{
				Cell = cell;
				EndpointId = endpointId ?? string.Empty;
				Container = container;
			}
		}

		private readonly struct Candidate
		{
			internal SpatialCandidateKey Key { get; }

			internal Container Container { get; }

			internal Candidate(SpatialCandidateKey key, Container container)
			{
				Key = key;
				Container = container;
			}
		}

		private sealed class CandidateComparer : IComparer<Candidate>
		{
			internal static CandidateComparer Instance { get; } = new CandidateComparer();

			public int Compare(Candidate left, Candidate right)
			{
				return left.Key.CompareTo(right.Key);
			}
		}

		private readonly object _gate = new object();

		private readonly Dictionary<CellKey, List<Entry>> _cells = new Dictionary<CellKey, List<Entry>>();

		private readonly Dictionary<int, Membership> _membership = new Dictionary<int, Membership>();

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

		internal void Add(Container container)
		{
			//IL_0018: 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_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)container == (Object)null)
			{
				return;
			}
			int instanceID;
			Vector3 position;
			try
			{
				instanceID = ((Object)container).GetInstanceID();
				position = ((Component)container).transform.position;
			}
			catch
			{
				return;
			}
			lock (_gate)
			{
				if (!Finite(position))
				{
					RemoveLocked(instanceID, container);
					return;
				}
				CellKey cellKey = CellKey.From(position);
				Membership value;
				bool num = _membership.TryGetValue(instanceID, out value);
				bool flag = num && value.Cell.Equals(cellKey);
				if (ContainerSpatialPolicy.CanRetain(exactContainerPresent: flag && _cells.TryGetValue(value.Cell, out var value2) && ContainsExact(value2, instanceID, container), membershipExists: num, cellUnchanged: flag, endpointUnchanged: !string.IsNullOrEmpty(value.EndpointId)))
				{
					return;
				}
				string endpointId;
				try
				{
					endpointId = ValheimContainerIdentity.EndpointId(container);
				}
				catch
				{
					return;
				}
				RemoveLocked(instanceID, container);
				if (ContainerSpatialPolicy.CanIndexEndpoint(endpointId))
				{
					if (!_cells.TryGetValue(cellKey, out value2))
					{
						value2 = new List<Entry>();
						_cells.Add(cellKey, value2);
					}
					value2.Add(new Entry(instanceID, container));
					_membership.Add(instanceID, new Membership(cellKey, endpointId, container));
					AddEndpoint(endpointId, instanceID);
				}
			}
		}

		internal int RefreshLoadedContainers()
		{
			Container[] array;
			try
			{
				array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
			}
			catch
			{
				return 0;
			}
			for (int i = 0; i < array.Length; i++)
			{
				Add(array[i]);
			}
			return array.Length;
		}

		internal void Remove(Container container)
		{
			if (container == null)
			{
				return;
			}
			int instanceID;
			try
			{
				instanceID = ((Object)container).GetInstanceID();
			}
			catch
			{
				return;
			}
			lock (_gate)
			{
				RemoveLocked(instanceID, container);
			}
		}

		internal IReadOnlyList<Container> Nearest(Vector3 origin, float radius, int maximum, out bool truncated)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			if (maximum <= 0)
			{
				throw new ArgumentOutOfRangeException("maximum");
			}
			if (!Finite(origin) || float.IsNaN(radius) || float.IsInfinity(radius) || radius <= 0f)
			{
				truncated = false;
				return Array.Empty<Container>();
			}
			radius = Math.Min(50f, radius);
			maximum = Math.Min(256, maximum);
			float num = radius * radius;
			CellKey cellKey = CellKey.From(origin - new Vector3(radius, 0f, radius));
			CellKey cellKey2 = CellKey.From(origin + new Vector3(radius, 0f, radius));
			SortedSet<Candidate> sortedSet = new SortedSet<Candidate>(CandidateComparer.Instance);
			int num2 = 0;
			lock (_gate)
			{
				for (int i = cellKey.X; i <= cellKey2.X; i++)
				{
					for (int j = cellKey.Z; j <= cellKey2.Z; j++)
					{
						CellKey key = new CellKey(i, j);
						if (!_cells.TryGetValue(key, out var value))
						{
							continue;
						}
						for (int num3 = value.Count - 1; num3 >= 0; num3--)
						{
							Entry entry = value[num3];
							Container container = entry.Container;
							if ((Object)(object)container == (Object)null)
							{
								value.RemoveAt(num3);
								RemoveIndexesLocked(entry.InstanceId);
							}
							else
							{
								try
								{
									if (((Behaviour)container).isActiveAndEnabled)
									{
										Vector3 val = ((Component)container).transform.position - origin;
										float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
										if (!float.IsNaN(sqrMagnitude) && !float.IsInfinity(sqrMagnitude) && !(sqrMagnitude > num) && _membership.TryGetValue(entry.InstanceId, out var value2) && ContainerSpatialPolicy.CanIndexEndpoint(value2.EndpointId) && _endpointMembers.TryGetValue(value2.EndpointId, out var value3) && ContainerSpatialPolicy.IsUniqueEndpointMemberCount(value3.Count))
										{
											if (num2 < int.MaxValue)
											{
												num2++;
											}
											string endpointId = value2.EndpointId;
											sortedSet.Add(new Candidate(new SpatialCandidateKey(sqrMagnitude, endpointId, entry.InstanceId), container));
											if (sortedSet.Count > maximum)
											{
												sortedSet.Remove(sortedSet.Max);
											}
										}
									}
								}
								catch
								{
								}
							}
						}
						if (value.Count == 0)
						{
							_cells.Remove(key);
						}
					}
				}
			}
			List<Container> list = new List<Container>(sortedSet.Count);
			foreach (Candidate item in sortedSet)
			{
				list.Add(item.Container);
			}
			truncated = num2 > maximum;
			return list.AsReadOnly();
		}

		internal bool TryGet(string endpointId, out Container container)
		{
			container = null;
			if (string.IsNullOrEmpty(endpointId))
			{
				return false;
			}
			lock (_gate)
			{
				if (!_endpointMembers.TryGetValue(endpointId, out var value) || !ContainerSpatialPolicy.IsUniqueEndpointMemberCount(value.Count))
				{
					return false;
				}
				using HashSet<int>.Enumerator enumerator = value.GetEnumerator();
				if (enumerator.MoveNext())
				{
					int current = enumerator.Current;
					if (!_membership.TryGetValue(current, out var value2) || !string.Equals(value2.EndpointId, endpointId, StringComparison.Ordinal) || (Object)(object)value2.Container == (Object)null)
					{
						RemoveIndexesLocked(current);
						return false;
					}
					container = value2.Container;
					return true;
				}
			}
			return false;
		}

		private void RemoveLocked(int instanceId, Container container)
		{
			if (!_membership.TryGetValue(instanceId, out var value))
			{
				return;
			}
			if (_cells.TryGetValue(value.Cell, out var value2))
			{
				for (int num = value2.Count - 1; num >= 0; num--)
				{
					if (value2[num].InstanceId == instanceId || value2[num].Container == container)
					{
						value2.RemoveAt(num);
					}
				}
				if (value2.Count == 0)
				{
					_cells.Remove(value.Cell);
				}
			}
			RemoveIndexesLocked(instanceId);
		}

		private void RemoveIndexesLocked(int instanceId)
		{
			if (!_membership.TryGetValue(instanceId, out var value))
			{
				return;
			}
			_membership.Remove(instanceId);
			if (!string.IsNullOrEmpty(value.EndpointId) && _endpointMembers.TryGetValue(value.EndpointId, out var value2))
			{
				value2.Remove(instanceId);
				if (value2.Count == 0)
				{
					_endpointMembers.Remove(value.EndpointId);
				}
			}
		}

		private void AddEndpoint(string endpointId, int instanceId)
		{
			if (!string.IsNullOrEmpty(endpointId))
			{
				if (!_endpointMembers.TryGetValue(endpointId, out var value))
				{
					value = new HashSet<int>();
					_endpointMembers.Add(endpointId, value);
				}
				value.Add(instanceId);
			}
		}

		private static bool ContainsExact(List<Entry> entries, int instanceId, Container container)
		{
			for (int i = 0; i < entries.Count; i++)
			{
				if (entries[i].InstanceId == instanceId && entries[i].Container == container)
				{
					return true;
				}
			}
			return false;
		}

		private static bool Finite(Vector3 value)
		{
			//IL_0000: 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_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: 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)
			if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z))
			{
				return !float.IsInfinity(value.z);
			}
			return false;
		}
	}
	internal static class ValheimContainerIdentity
	{
		internal unsafe static string EndpointId(Container container)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)container == (Object)null)
			{
				return string.Empty;
			}
			ZNetView val = NetworkView(container);
			ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
			if (val2 != null)
			{
				ZDOID uid = val2.m_uid;
				return "valheim.zdo:" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString();
			}
			return string.Empty;
		}

		internal static ZNetView NetworkView(Container container)
		{
			if ((Object)(object)container == (Object)null)
			{
				return null;
			}
			if (!((Object)(object)container.m_rootObjectOverride != (Object)null))
			{
				return ((Component)container).GetComponent<ZNetView>();
			}
			return container.m_rootObjectOverride;
		}

		internal static string TypeId(Container container)
		{
			string text = (((Object)(object)container == (Object)null) ? "container" : ((Object)((Component)container).gameObject).name);
			if (text.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				text = text.Substring(0, text.Length - "(Clone)".Length);
			}
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text.Trim();
			}
			return "container";
		}

		internal static string ResourceId(ItemData item)
		{
			if (item == null)
			{
				return string.Empty;
			}
			if ((Object)(object)item.m_dropPrefab != (Object)null && !string.IsNullOrWhiteSpace(((Object)item.m_dropPrefab).name))
			{
				string text = ((Object)item.m_dropPrefab).name;
				if (text.EndsWith("(Clone)", StringComparison.Ordinal))
				{
					text = text.Substring(0, text.Length - "(Clone)".Length);
				}
				return text.Trim();
			}
			if (item.m_shared != null && !string.IsNullOrWhiteSpace(item.m_shared.m_name))
			{
				return item.m_shared.m_name.Trim();
			}
			return string.Empty;
		}
	}
	[HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")]
	internal static class StorageSearchCursorLeasePatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			StorageSearchPanel.RenewCursorLease();
		}
	}
	[HarmonyPatch(typeof(Player), "TakeInput")]
	internal static class StorageSearchInputGatePatch
	{
		[HarmonyAfter(new string[] { "chazman.RunicBuildCamera" })]
		private static void Postfix(Player __instance, ref bool __result)
		{
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && Plugin.SearchPanelOpen)
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class ContainerAwakePatch
	{
		private static void Postfix(Container __instance)
		{
			ContainerHoverContents.Invalidate(__instance);
			Plugin.Index?.Add(__instance);
		}
	}
	[HarmonyPatch(typeof(Container), "OnDestroyed")]
	internal static class ContainerDestroyPatch
	{
		private static void Postfix(Container __instance, bool __runOriginal)
		{
			if (__runOriginal)
			{
				ContainerHoverContents.Invalidate(__instance);
				Plugin.Index?.Remove(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Container), "CheckForChanges")]
	internal static class ContainerSpatialRefreshPatch
	{
		private static void Postfix(Container __instance)
		{
			Plugin.Index?.Add(__instance);
		}
	}
	[HarmonyPatch(typeof(Container), "GetHoverText", new Type[] { })]
	internal static class ContainerHoverTextPatch
	{
		[HarmonyPriority(0)]
		private static void Postfix(Container __instance, ref string __result)
		{
			try
			{
				ContainerHoverContents.Append(__instance, ref __result);
			}
			catch
			{
			}
		}
	}
	internal sealed class ControllerBindingState
	{
		internal string Signature { get; }

		internal string ModifierName { get; }

		internal string QuickStackName { get; }

		internal string RestockName { get; }

		internal string SortName { get; }

		internal string ConsolidateName { get; }

		internal string SearchName { get; }

		internal ButtonDef Modifier { get; }

		internal ButtonDef QuickStack { get; }

		internal ButtonDef Restock { get; }

		internal ButtonDef Sort { get; }

		internal ButtonDef Consolidate { get; }

		internal ButtonDef Search { get; }

		internal bool ModifierValid => Modifier != null;

		internal bool QuickStackValid => QuickStack != null;

		internal bool RestockValid => Restock != null;

		internal bool SortValid => Sort != null;

		internal bool ConsolidateValid => Consolidate != null;

		internal bool SearchValid => Search != null;

		internal int ValidRouteCount => (QuickStackValid ? 1 : 0) + (RestockValid ? 1 : 0) + (SortValid ? 1 : 0) + (ConsolidateValid ? 1 : 0) + (SearchValid ? 1 : 0);

		internal ControllerBindingState(string signature, string modifierName, string quickStackName, string restockName, string sortName, string consolidateName, string searchName, ButtonDef modifier, ButtonDef quickStack, ButtonDef restock, ButtonDef sort, ButtonDef consolidate, ButtonDef search)
		{
			Signature = signature;
			ModifierName = modifierName;
			QuickStackName = quickStackName;
			RestockName = restockName;
			SortName = sortName;
			ConsolidateName = consolidateName;
			SearchName = searchName;
			Modifier = modifier;
			QuickStack = quickStack;
			Restock = restock;
			Sort = sort;
			Consolidate = consolidate;
			Search = search;
		}
	}
	internal static class StorageControllerCollisionGuard
	{
		private static StorageActionEdges _pendingEdge;

		private static ButtonDef _latchedModifier;

		private static ControllerBindingState _sessionBindings;

		private static ControllerSessionPaths _sessionPaths;

		private static int _releasedFrame = -1;

		private static int _capturedFrame = -1;

		private static int _lastProbeFrame = -1;

		internal static StorageActionEdges ObserveAndConsume(ControllerBindingState bindings, StorageRouteContext context)
		{
			Observe(bindings, context);
			return ConsumePendingEdge();
		}

		internal static StorageActionEdges ConsumePendingEdge()
		{
			StorageActionEdges pendingEdge = _pendingEdge;
			_pendingEdge = StorageActionEdges.None;
			return pendingEdge;
		}

		internal static bool ShouldSuppress(string buttonName)
		{
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Invalid comparison between Unknown and I4
			ConfigEntry<bool> enabled = PluginConfig.Enabled;
			if (enabled == null || !enabled.Value)
			{
				return false;
			}
			UpdateReleaseState();
			ConfigEntry<bool> controllerShortcuts = PluginConfig.ControllerShortcuts;
			if (controllerShortcuts != null && controllerShortcuts.Value && ZInput.instance != null)
			{
				bool flag;
				if (_latchedModifier != null)
				{
					flag = _latchedModifier.Held;
				}
				else
				{
					string text = (PluginConfig.ControllerModifier?.Value ?? string.Empty).Trim();
					ButtonDef val = ((text.Length == 0) ? null : ZInput.instance.GetButtonDef(text));
					flag = val != null && val.Held;
				}
				if (flag && _lastProbeFrame != Time.frameCount)
				{
					_lastProbeFrame = Time.frameCount;
					Observe(_sessionBindings ?? StorageControllerBindings.Resolve(), Plugin.CaptureRouteContext());
				}
			}
			if (_latchedModifier == null || ZInput.instance == null)
			{
				return false;
			}
			ButtonDef buttonDef = ZInput.instance.GetButtonDef(buttonName);
			if (buttonDef == null || (int)buttonDef.Source != 180)
			{
				return false;
			}
			string actionPath = buttonDef.GetActionPath(true);
			if (actionPath != null)
			{
				return _sessionPaths.Contains(actionPath);
			}
			return false;
		}

		internal static void Reset()
		{
			_pendingEdge = StorageActionEdges.None;
			_latchedModifier = null;
			_sessionBindings = null;
			_sessionPaths = default(ControllerSessionPaths);
			_releasedFrame = -1;
			_capturedFrame = -1;
			_lastProbeFrame = -1;
		}

		private static void Observe(ControllerBindingState bindings, StorageRouteContext context)
		{
			UpdateReleaseState();
			if (bindings == null || !bindings.ModifierValid || !bindings.Modifier.Held || _capturedFrame == Time.frameCount)
			{
				return;
			}
			StorageActionEdges storageActionEdges = StorageActionEdges.None;
			if (bindings.SortValid && bindings.Sort.Pressed)
			{
				storageActionEdges = StorageActionEdges.ControllerSort;
			}
			else if (bindings.QuickStackValid && bindings.QuickStack.Pressed)
			{
				storageActionEdges = StorageActionEdges.ControllerQuickStack;
			}
			else if (bindings.RestockValid && bindings.Restock.Pressed)
			{
				storageActionEdges = StorageActionEdges.ControllerRestock;
			}
			else if (bindings.ConsolidateValid && bindings.Consolidate.Pressed)
			{
				storageActionEdges = StorageActionEdges.ControllerConsolidate;
			}
			else if (bindings.SearchValid && bindings.Search.Pressed)
			{
				storageActionEdges = StorageActionEdges.ControllerSearch;
			}
			if (storageActionEdges == StorageActionEdges.None)
			{
				return;
			}
			_capturedFrame = Time.frameCount;
			_pendingEdge = storageActionEdges;
			StorageActionRequest request = StorageActionRouter.Select(storageActionEdges);
			StorageRouteDecision decision = StorageActionRouter.Route(request, context);
			bool flag = _latchedModifier != null;
			if (ControllerChordSessionPolicy.Decide(flag, decision) == ControllerChordDisposition.ReportWithoutConsume)
			{
				return;
			}
			if (!flag)
			{
				StartSession(bindings);
			}
			_releasedFrame = -1;
			if (PluginConfig.DebugTransfers.Value)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)("input-consumed source=controller action=" + StorageActionDiagnostics.ActionCode(request.Action) + " result=" + ((decision.Outcome == StorageRouteOutcome.Execute) ? "authorized" : "blocked-in-owned-session") + " modifier-session=active configured-button-aliases=suppressed-until-full-release"));
				}
			}
		}

		private static void UpdateReleaseState()
		{
			if (_latchedModifier != null)
			{
				if (_latchedModifier.Held || AnySessionPrimaryHeld())
				{
					_releasedFrame = -1;
				}
				else if (_releasedFrame < 0)
				{
					_releasedFrame = Time.frameCount;
				}
				else if (_releasedFrame != Time.frameCount)
				{
					_latchedModifier = null;
					_sessionBindings = null;
					_sessionPaths = default(ControllerSessionPaths);
					_releasedFrame = -1;
				}
			}
		}

		private static void StartSession(ControllerBindingState bindings)
		{
			_latchedModifier = bindings.Modifier;
			_sessionBindings = bindings;
			_sessionPaths = new ControllerSessionPaths(Path(bindings.Modifier), Path(bindings.QuickStack), Path(bindings.Restock), Path(bindings.Sort), Path(bindings.Consolidate), Path(bindings.Search));
		}

		private static bool AnySessionPrimaryHeld()
		{
			if (!IsHeld(_sessionBindings?.QuickStack) && !IsHeld(_sessionBindings?.Restock) && !IsHeld(_sessionBindings?.Sort) && !IsHeld(_sessionBindings?.Consolidate))
			{
				return IsHeld(_sessionBindings?.Search);
			}
			return true;
		}

		private static bool IsHeld(ButtonDef definition)
		{
			if (definition != null)
			{
				return definition.Held;
			}
			return false;
		}

		private static string Path(ButtonDef definition)
		{
			return ((definition != null) ? definition.GetActionPath(true) : null) ?? string.Empty;
		}
	}
	[HarmonyPatch(typeof(ZInput), "GetButton", new Type[] { typeof(string) })]
	internal static class StorageControllerGetButtonPatch
	{
		[HarmonyPriority(800)]
		[HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })]
		private static bool Prefix(string name, ref bool __result)
		{
			return AllowOrConsume(name, ref __result);
		}

		private static bool AllowOrConsume(string name, ref bool result)
		{
			if (StorageSearchGameplayInputGuard.ShouldSuppressPrimaryAttack(name))
			{
				result = false;
				return false;
			}
			if (!StorageControllerCollisionGuard.ShouldSuppress(name))
			{
				return true;
			}
			result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(ZInput), "GetButtonDown", new Type[] { typeof(string) })]
	internal static class StorageControllerGetButtonDownPatch
	{
		[HarmonyPriority(800)]
		[HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })]
		private static bool Prefix(string name, ref bool __result)
		{
			if (StorageSearchGameplayInputGuard.ShouldSuppressPrimaryAttack(name))
			{
				__result = false;
				return false;
			}
			if (!StorageControllerCollisionGuard.ShouldSuppress(name))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(ZInput), "GetButtonUp", new Type[] { typeof(string) })]
	internal static class StorageControllerGetButtonUpPatch
	{
		[HarmonyPriority(800)]
		[HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })]
		private static bool Prefix(string name, ref bool __result)
		{
			if (StorageSearchGameplayInputGuard.ShouldSuppressPrimaryAttack(name))
			{
				__result = false;
				return false;
			}
			if (!StorageControllerCollisionGuard.ShouldSuppress(name))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(ZInput), "GetButtonPressedTimer", new Type[] { typeof(string) })]
	internal static class StorageControllerPressedTimerPatch
	{
		[HarmonyPriority(800)]
		[HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })]
		private static bool Prefix(string name, ref float __result)
		{
			if (StorageSearchGameplayInputGuard.ShouldSuppressPrimaryAttack(name))
			{
				__result = 0f;
				return false;
			}
			if (!StorageControllerCollisionGuard.ShouldSuppress(name))
			{
				return true;
			}
			__result = 0f;
			return false;
		}
	}
	[HarmonyPatch(typeof(ZInput), "GetButtonLastPressedTimer", new Type[] { typeof(string) })]
	internal static class StorageControllerLastPressedTimerPatch
	{
		[HarmonyPriority(800)]
		[HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })]
		private static bool Prefix(string name, ref float __result)
		{
			if (StorageSearchGameplayInputGuard.ShouldSuppressPrimaryAttack(name))
			{
				__result = 0f;
				return false;
			}
			if (!StorageControllerCollisionGuard.ShouldSuppress(name))
			{
				return true;
			}
			__result = 0f;
			return false;
		}
	}
	[HarmonyPatch(typeof(ZInput), "GetKeyDown", new Type[]
	{
		typeof(KeyCode),
		typeof(bool)
	})]
	internal static class StorageSearchEscapeKeyPatch
	{
		[HarmonyPriority(800)]
		private static bool Prefix(KeyCode key, ref bool __result)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!StorageSearchGameplayInputGuard.ShouldSuppressEscape(key))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	internal sealed class RaycastHitDistanceComparer : IComparer<RaycastHit>
	{
		internal static readonly RaycastHitDistanceComparer Instance = new RaycastHitDistanceComparer();

		public int Compare(RaycastHit left, RaycastHit right)
		{
			return ((RaycastHit)(ref left)).distance.CompareTo(((RaycastHit)(ref right)).distance);
		}
	}
	internal sealed class StorageActions
	{
		private sealed class SearchAccumulator
		{
			private readonly List<Container> _containers = new List<Container>();

			internal string ResourceId { get; }

			internal string DisplayName { get; }

			internal int Quantity { get; set; }

			internal SearchAccumulator(string resourceId, string displayName)
			{
				ResourceId = resourceId;
				DisplayName = displayName;
			}

			internal void AddContainer(Container container)
			{
				if ((Object)(object)container != (Object)null && !_containers.Contains(container))
				{
					_containers.Add(container);
				}
			}

			internal StorageSearchEntry ToEntry()
			{
				return new StorageSearchEntry(ResourceId, DisplayName, Quantity, _containers.AsReadOnly());
			}
		}

		private static readonly FieldInfo CurrentContainerField = AccessTools.Field(typeof(InventoryGui), "m_currentContainer");

		private static readonly MethodInfo InventoryChangedMethod = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null);

		private readonly ContainerIndex _index;

		private readonly StorageSearchPanel _searchPanel;

		internal StorageActions(ContainerIndex index, StorageSearchPanel searchPanel)
		{
			_index = index ?? throw new ArgumentNullException("index");
			_searchPanel = searchPanel ?? throw new ArgumentNullException("searchPanel");
		}

		internal void QuickStack()
		{
			if (!TryBeginMutation("Quick Stack", out var player, out var mutationLease))
			{
				return;
			}
			using (mutationLease)
			{
				Inventory inventory = ((Humanoid)player).GetInventory();
				IReadOnlyList<Container> readOnlyList = Nearby(player, requireWritable: true, "quick-stack");
				int num = 0;
				int num2 = 0;
				int num3 = 0;
				int num4 = 0;
				int num5 = 0;
				int num6 = 0;
				int num7 = 0;
				int num8 = 0;
				Dictionary<Container, Inventory> dictionary = new Dictionary<Container, Inventory>();
				HashSet<Container> hashSet = new HashSet<Container>();
				List<ItemData> list = new List<ItemData>(inventory.GetAllItems());
				list.Sort(CompareGridPosition);
				if (!TryCaptureProtection(list, out var snapshot, out var failureCode))
				{
					Message(player, "Runic Storage: carried-item protection could not be proven; Quick Stack changed nothing.");
					LogAction("quick-stack", failureCode, $"stacks={list.Count} moved=0");
					return;
				}
				int num9 = 0;
				for (int i = 0; i < list.Count; i++)
				{
					ItemData val = list[i];
					StorageProtectionState storageProtectionState = snapshot.StateAt(i);
					if (IsProtected(player, val, storageProtectionState))
					{
						num4++;
						if (storageProtectionState == StorageProtectionState.Locked)
						{
							num9++;
						}
						if (val != null && (val.m_equipped || ((Humanoid)player).IsItemEquiped(val)))
						{
							num6++;
						}
						else if (val != null && PluginConfig.ProtectHotbar.Value && val.m_gridPos.y == 0)
						{
							num5++;
						}
						continue;
					}
					string text = ValheimContainerIdentity.ResourceId(val);
					if (text.Length == 0)
					{
						continue;
					}
					num3++;
					int stack = val.m_stack;
					bool flag = false;
					foreach (Container item in readOnlyList)
					{
						if (val.m_stack <= 0)
						{
							break;
						}
						if (!dictionary.TryGetValue(item, out var value))
						{
							if (hashSet.Contains(item))
							{
								continue;
							}
							if (!StorageContainerAuthority.TryGetSynchronizedServerInventory(item, out value))
							{
								hashSet.Add(item);
								num8++;
								LogTransfer(PlayerEndpointId(player), ValheimContainerIdentity.EndpointId(item), text, 0, "ownership.denied");
								continue;
							}
							dictionary[item] = value;
						}
						if (ContainsResource(value, text))
						{
							if (!flag)
							{
								num2 = AddSaturated(num2, stack);
								num7++;
								flag = true;
							}
							int stack2 = val.m_stack;
							int num10 = ValheimContainerService.MoveUpTo(inventory, value, val, stack2, player, mutationLease, null, item);
							num = AddSaturated(num, num10);
							LogTransfer(PlayerEndpointId(player), ValheimContainerIdentity.EndpointId(item), text, num10, (num10 > 0) ? "ok" : "destination.full");
							if (num10 >= stack2)
							{
								break;
							}
						}
					}
				}
				QuickStackNoOpReason reason = QuickStackDiagnostics.Classify(new QuickStackObservation(list.Count, num3, num4, readOnlyList.Count, num7, num));
				int num11 = Math.Max(0, num2 - num);
				string text2 = ((num > 0) ? $"Runic Storage: moved {num} item(s); {num11} matching item(s) remained." : QuickStackNoOpFeedback(reason, num5, num6, num9));
				Message(player, text2);
				LogAction("quick-stack", (num > 0) ? "ok" : QuickStackDiagnostics.ReasonCode(reason), $"carriedStacks={list.Count} eligibleStacks={num3} protectedStacks={num4} " + $"hotbarProtected={num5} equippedProtected={num6} itemLockProtected={num9} " + $"authorizedContainers={readOnlyList.Count} matchedStacks={num7} " + $"ownershipDenied={num8} moved={num} remainder={num11}");
			}
		}

		internal void StoreAllOpenedContainer()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			if (!TryBeginMutation("Store All", out var player, out var mutationLease))
			{
				return;
			}
			using (mutationLease)
			{
				Container val = (Container)(((Object)InventoryGui.instance == (Object)null || CurrentContainerField == null) ? null : /*isinst with value type is only supported in some contexts*/);
				if ((Object)val == (Object)null)
				{
					Message(player, "Runic Storage: open a container before using Store All.");
					LogAction("store-all-opened-container", "ui.open-container-required", "container=false moved=0");
					return;
				}
				if ((int)val.m_privacy == 0 || !ValheimContainerService.CanDiscover(val, player.GetPlayerID(), requireWritable: true, allowCurrentUse: true))
				{
					Message(player, "Runic Storage: that container is personal, busy, or denied; Store All changed nothing.");
					LogAction("store-all-opened-container", "container.denied", "authorized=false moved=0");
					return;
				}
				if (!StorageContainerAuthority.TryGetExactOpenedLocalOwnerInventory(val, player, out var inventory))
				{
					Message(player, "Runic Storage: ownership changed before Store All; nothing was