Decompiled source of VikingQoL v1.3.2

plugins/VikingQoL.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.UI;
using Valheim.SettingsGui;
using Valheim.UI;
using VikingQoL.Core;
using VikingQoL.Modules.Building;
using VikingQoL.Modules.Map;
using VikingQoL.Modules.SettingsUI;
using VikingQoL.Modules.Stations;
using VikingQoL.Modules.Storage;
using VikingQoL.Modules.Storage.Craft;
using VikingQoL.Modules.Storage.Extras;
using VikingQoL.Modules.Storage.Shared;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_guiutils")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("VikingQoL")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.3.2.0")]
[assembly: AssemblyInformationalVersion("1.3.2")]
[assembly: AssemblyProduct("VikingQoL")]
[assembly: AssemblyTitle("VikingQoL")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace VikingQoL
{
	[BepInPlugin("ff15vikings.vikingqol", "VikingQoL", "1.3.2")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "ff15vikings.vikingqol";

		public const string Name = "VikingQoL";

		public const string Version = "1.3.2";

		private Harmony _harmony;

		internal static Plugin Instance { get; private set; }

		internal static bool IsHeadless => (int)SystemInfo.graphicsDeviceType == 4;

		private void Awake()
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Expected O, but got Unknown
			Instance = this;
			Log.Init(((BaseUnityPlugin)this).Logger);
			MigrateOldConfig();
			ModConfig.Init(((BaseUnityPlugin)this).Config);
			StorageConfig.Bind(((BaseUnityPlugin)this).Config);
			StationsConfig.Bind(((BaseUnityPlugin)this).Config);
			BuildingConfig.Bind(((BaseUnityPlugin)this).Config);
			MapConfig.Bind(((BaseUnityPlugin)this).Config);
			SettingsUIConfig.Bind(((BaseUnityPlugin)this).Config);
			ModConfig.FinishBinding();
			ConfigWatcher.Start(((BaseUnityPlugin)this).Config);
			StackSizes.Init();
			PortalPins.Init();
			PlayerColors.Init();
			Beacons.Init();
			_harmony = new Harmony("ff15vikings.vikingqol");
			Patch(typeof(VersionCheck), "Core.VersionCheck", required: true);
			Patch(typeof(ConsoleCommands), "Core.ConsoleCommands", required: false);
			if (!IsHeadless)
			{
				Patch(typeof(VersionCheck.ConnectErrorPatch), "Core.ConnectErrorMessage", required: false);
			}
			Patch(typeof(KeepOversizedStacks), "Storage.KeepOversizedStacks", required: true);
			Patch(typeof(StackSizes), "Storage.StackSizes", required: false);
			Patch(typeof(ChestRules), "Storage.ChestRules", required: false);
			Patch(typeof(ContainerService), "Storage.ContainerService", required: false);
			Patch(typeof(ChestViewers), "Storage.ChestViewers", required: false);
			Patch(typeof(ChestLabels), "Storage.ChestLabels", required: false);
			Patch(typeof(ChestCrafting), "Storage.ChestCrafting", required: false);
			Patch(typeof(ChestLocks), "Storage.ChestLocks", required: false);
			Patch(typeof(PortalPins), "Map.PortalPins", required: false);
			Patch(typeof(PlayerColors), "Map.PlayerColors", required: false);
			Patch(typeof(Beacons), "Map.Beacons", required: false);
			if (!IsHeadless)
			{
				Patch(typeof(SharedChestUI), "Storage.SharedChestUI", required: false);
				Patch(typeof(NearbyChests), "Storage.NearbyChests", required: false);
				Patch(typeof(CraftFromChests), "Storage.CraftFromChests", required: false);
				Patch(typeof(StationsFromChests), "Storage.StationsFromChests", required: false);
				Patch(typeof(StationItemChoice), "Storage.StationItemChoice", required: false);
				Patch(typeof(HiddenItemsMessage), "Storage.HiddenItemsMessage", required: false);
				Patch(typeof(RecipeListUI), "Storage.RecipeListUI", required: false);
				Patch(typeof(SearchBoxes), "Storage.SearchBoxes", required: false);
				Patch(typeof(SearchKeyBlock), "Storage.SearchKeyBlock", required: false);
				Patch(typeof(StationRadialMenu), "Storage.StationRadialMenu", required: false);
				Patch(typeof(WeightAndTooltip), "Storage.WeightAndTooltip", required: false);
				Patch(typeof(InventoryButtons), "Storage.InventoryButtons", required: false);
				Patch(typeof(LockedSlots), "Storage.LockedSlots", required: false);
				Patch(typeof(LockedSlotsUI), "Storage.LockedSlotsUI", required: false);
				Patch(typeof(ChestHover), "Storage.ChestHover", required: false);
				Patch(typeof(ChestLabelsUI), "Storage.ChestLabelsUI", required: false);
				Patch(typeof(ChestPeekIcons), "Storage.ChestPeekIcons", required: false);
				Patch(typeof(BulkAdd), "Stations.BulkAdd", required: false);
				Patch(typeof(KilnWoodOnly), "Stations.KilnWoodOnly", required: false);
				Patch(typeof(StationTimers), "Stations.StationTimers", required: false);
				Patch(typeof(CreatureTimers), "Stations.CreatureTimers", required: false);
				Patch(typeof(ProducerTimers), "Stations.ProducerTimers", required: false);
				Patch(typeof(BuildCamera), "Building.BuildCamera", required: false);
				Patch(typeof(MapPinColors), "Map.MapPinColors", required: false);
				Patch(typeof(PlayerColorsMap), "Map.PlayerColorsMap", required: false);
				Patch(typeof(BeaconsClient), "Map.BeaconsClient", required: false);
				Patch(typeof(LookPing), "Map.LookPing", required: false);
				Patch(typeof(SettingsMenuButton), "SettingsUI.MenuButton", required: false);
			}
			Log.Info("VikingQoL 1.3.2 loaded (" + (IsHeadless ? "dedicated server" : "client") + ").");
		}

		private void Start()
		{
			ConflictCheck.Run();
		}

		private void MigrateOldConfig()
		{
			string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
			if (File.Exists(configFilePath))
			{
				return;
			}
			try
			{
				string[] files = Directory.GetFiles(Paths.ConfigPath, "*.vikingqol.cfg");
				if (files.Length == 1)
				{
					File.Move(files[0], configFilePath);
					((BaseUnityPlugin)this).Config.Reload();
					Log.Info("Settings moved from " + Path.GetFileName(files[0]) + " to " + Path.GetFileName(configFilePath) + ".");
				}
			}
			catch (Exception ex)
			{
				Log.Warn("Couldn't move the old settings file, using default settings: " + ex.Message);
			}
		}

		private void Update()
		{
			ConfigWatcher.Tick();
			StackSizes.Tick();
			PortalPins.ServerTick();
			PlayerColors.Tick();
			Beacons.Tick();
			if (!IsHeadless)
			{
				ContainerService.Tick();
				ChestViewers.Tick();
				StorageExtras.TickHotkeys();
				BuildCamera.Tick();
				BuildReach.Tick();
				EnemyMarkers.Tick();
				Clock.Tick();
				PortalPinsClient.Tick();
				BeaconsClient.Tick();
				LookPing.Tick();
				ChestLabels.Tick();
				ChestCrafting.Tick();
				ChestLocks.Tick();
				ChestPeekIcons.Tick();
				SettingsWindow.Tick();
			}
		}

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

		private void Patch(Type patchClass, string feature, bool required)
		{
			try
			{
				_harmony.CreateClassProcessor(patchClass).Patch();
				Log.Debug("Patched " + feature);
			}
			catch (Exception arg)
			{
				Log.Error(string.Format("Failed to patch {0}{1}: {2}", feature, required ? " (required!)" : ", feature disabled", arg));
			}
		}
	}
}
namespace VikingQoL.Modules.Storage
{
	[HarmonyPatch]
	internal static class KeepOversizedStacks
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(Inventory), "AddItem", new Type[14]
			{
				typeof(int),
				typeof(int),
				typeof(float),
				typeof(Vector2i),
				typeof(bool),
				typeof(int),
				typeof(int),
				typeof(long),
				typeof(string),
				typeof(Dictionary<string, string>),
				typeof(int),
				typeof(bool),
				typeof(bool),
				typeof(bool)
			}, (Type[])null);
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(Mathf), "Min", new Type[2]
			{
				typeof(int),
				typeof(int)
			}, (Type[])null);
			MethodInfo operand = AccessTools.Method(typeof(KeepOversizedStacks), "KeepStack", (Type[])null, (Type[])null);
			List<CodeInstruction> list = instructions.ToList();
			int num = 0;
			foreach (CodeInstruction item in list)
			{
				if (CodeInstructionExtensions.Calls(item, methodInfo))
				{
					item.opcode = OpCodes.Call;
					item.operand = operand;
					num++;
				}
			}
			if (num != 1)
			{
				throw new InvalidOperationException($"expected 1 stack clamp in Inventory.AddItem, found {num}");
			}
			return list;
		}

		private static int KeepStack(int stack, int maxStackSize)
		{
			if (stack > maxStackSize)
			{
				Log.Debug($"Kept oversized stack: {stack} (max {maxStackSize}).");
			}
			return stack;
		}
	}
	[HarmonyPatch]
	internal static class StackSizes
	{
		internal const int SaveFormatLimit = 65535;

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

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

		private static bool _dirty;

		internal static void Init()
		{
			Action value = delegate
			{
				_dirty = true;
			};
			StorageConfig.StacksEnabled.Changed += value;
			StorageConfig.StackMultiplier.Changed += value;
			StorageConfig.MaterialMultiplier.Changed += value;
			StorageConfig.ConsumableMultiplier.Changed += value;
			StorageConfig.AmmoMultiplier.Changed += value;
			StorageConfig.TrophyMultiplier.Changed += value;
			StorageConfig.ItemOverrides.Changed += value;
			StorageConfig.ExcludedItems.Changed += value;
		}

		internal static void Tick()
		{
			if (_dirty && !((Object)(object)ObjectDB.instance == (Object)null))
			{
				_dirty = false;
				Apply(ObjectDB.instance, refreshLiveItems: true);
			}
		}

		[HarmonyPatch(typeof(ObjectDB), "Awake")]
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void ObjectDBAwake(ObjectDB __instance)
		{
			Apply(__instance, refreshLiveItems: false);
		}

		[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void ObjectDBCopy(ObjectDB __instance)
		{
			Apply(__instance, refreshLiveItems: false);
		}

		internal static void Apply(ObjectDB db, bool refreshLiveItems)
		{
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			if (db.m_items.Count == 0)
			{
				return;
			}
			Dictionary<string, int> overrides = ParseOverrides(StorageConfig.ItemOverrides.Value);
			HashSet<string> excluded = ParseNames(StorageConfig.ExcludedItems.Value);
			int num = 0;
			foreach (GameObject item in db.m_items)
			{
				ItemDrop val = (Object.op_Implicit((Object)(object)item) ? item.GetComponent<ItemDrop>() : null);
				if (!((Object)(object)val == (Object)null))
				{
					SharedData shared = val.m_itemData.m_shared;
					if (!VanillaByPrefab.TryGetValue(((Object)item).name, out var value))
					{
						value = shared.m_maxStackSize;
						VanillaByPrefab[((Object)item).name] = value;
					}
					int num2 = (shared.m_maxStackSize = Compute(((Object)item).name, shared.m_itemType, value, overrides, excluded));
					MaxBySharedName[shared.m_name] = num2;
					if (num2 > value)
					{
						num++;
					}
				}
			}
			if (refreshLiveItems)
			{
				RefreshLiveItems();
			}
			Log.Info(StorageConfig.StacksEnabled.Value ? $"Stack sizes applied: {num} items enlarged (multiplier {StorageConfig.StackMultiplier.Value}x)." : "Stack sizes: feature disabled, vanilla sizes in use.");
		}

		internal static int Compute(string prefabName, ItemType type, int vanilla, Dictionary<string, int> overrides, HashSet<string> excluded)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (vanilla <= 1)
			{
				return vanilla;
			}
			if (!StorageConfig.StacksEnabled.Value || excluded.Contains(prefabName))
			{
				return vanilla;
			}
			if (overrides.TryGetValue(prefabName, out var value))
			{
				return Mathf.Clamp(value, 1, 65535);
			}
			int num = CategoryMultiplier(type);
			if (num <= 0)
			{
				num = StorageConfig.StackMultiplier.Value;
			}
			return (int)Math.Min((long)vanilla * (long)num, 65535L);
		}

		private static int CategoryMultiplier(ItemType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Invalid comparison between Unknown and I4
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Invalid comparison between Unknown and I4
			if ((int)type <= 2)
			{
				if ((int)type != 1)
				{
					if ((int)type != 2)
					{
						goto IL_004b;
					}
					return StorageConfig.ConsumableMultiplier.Value;
				}
				return StorageConfig.MaterialMultiplier.Value;
			}
			if ((int)type != 9)
			{
				if ((int)type == 13)
				{
					return StorageConfig.TrophyMultiplier.Value;
				}
				if ((int)type != 23)
				{
					goto IL_004b;
				}
			}
			return StorageConfig.AmmoMultiplier.Value;
			IL_004b:
			return 0;
		}

		private static void RefreshLiveItems()
		{
			foreach (ItemDrop s_instance in ItemDrop.s_instances)
			{
				if (Object.op_Implicit((Object)(object)s_instance))
				{
					Refresh(s_instance.m_itemData);
				}
			}
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				RefreshInventory(((Humanoid)Player.m_localPlayer).GetInventory());
			}
			Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
			for (int i = 0; i < array.Length; i++)
			{
				RefreshInventory(array[i].GetInventory());
			}
			HotkeyBar[] array2 = Object.FindObjectsByType<HotkeyBar>((FindObjectsSortMode)0);
			for (int i = 0; i < array2.Length; i++)
			{
				foreach (ElementData element in array2[i].m_elements)
				{
					element.m_stackText = -1;
				}
			}
		}

		private static void RefreshInventory(Inventory inventory)
		{
			if (inventory == null)
			{
				return;
			}
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				Refresh(allItem);
			}
		}

		private static void Refresh(ItemData item)
		{
			if (item?.m_shared != null && MaxBySharedName.TryGetValue(item.m_shared.m_name, out var value))
			{
				item.m_shared.m_maxStackSize = value;
			}
		}

		internal static bool TryDescribe(string prefabName, out string description)
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			description = null;
			GameObject val = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.GetItemPrefab(prefabName) : null);
			ItemDrop val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponent<ItemDrop>() : null);
			if ((Object)(object)val2 == (Object)null)
			{
				return false;
			}
			int maxStackSize = val2.m_itemData.m_shared.m_maxStackSize;
			int value;
			int num = (VanillaByPrefab.TryGetValue(((Object)val).name, out value) ? value : maxStackSize);
			description = $"{((Object)val).name}: max stack {maxStackSize} (vanilla {num}, type {val2.m_itemData.m_shared.m_itemType})";
			return true;
		}

		private static Dictionary<string, int> ParseOverrides(string text)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
			foreach (string item in Split(text))
			{
				string[] array = item.Split(':');
				if (array.Length == 2 && int.TryParse(array[1].Trim(), out var result) && result > 0)
				{
					dictionary[array[0].Trim()] = result;
				}
				else
				{
					Log.Warn("Stacks.ItemOverrides: ignoring '" + item + "' (expected Name:Amount, e.g. Wood:1000).");
				}
			}
			return dictionary;
		}

		private static HashSet<string> ParseNames(string text)
		{
			return new HashSet<string>(Split(text), StringComparer.OrdinalIgnoreCase);
		}

		private static IEnumerable<string> Split(string text)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				yield break;
			}
			string[] array = text.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length > 0)
				{
					yield return text2;
				}
			}
		}
	}
	internal enum SortMode
	{
		Name,
		Type,
		Weight
	}
	internal enum RecipeSortMode
	{
		Original,
		Name,
		Type,
		Weight
	}
	internal enum ChestPeekStyle
	{
		Icons,
		Text
	}
	internal static class StorageConfig
	{
		internal static SyncedEntry<bool> StacksEnabled;

		internal static SyncedEntry<int> StackMultiplier;

		internal static SyncedEntry<int> MaterialMultiplier;

		internal static SyncedEntry<int> ConsumableMultiplier;

		internal static SyncedEntry<int> AmmoMultiplier;

		internal static SyncedEntry<int> TrophyMultiplier;

		internal static SyncedEntry<string> ItemOverrides;

		internal static SyncedEntry<string> ExcludedItems;

		internal static SyncedEntry<bool> SharedChestsEnabled;

		internal static SyncedEntry<bool> ShowViewersInHoverText;

		internal static SyncedEntry<bool> IgnorePersonalChestLock;

		internal static SyncedEntry<bool> IgnoreWardOnChests;

		internal static SyncedEntry<bool> CraftFromChestsEnabled;

		internal static SyncedEntry<float> CraftingRadius;

		internal static SyncedEntry<float> BuildingRadius;

		internal static SyncedEntry<float> StationRadius;

		internal static SyncedEntry<bool> InventoryFirst;

		internal static SyncedEntry<bool> QuickStackEnabled;

		internal static SyncedEntry<float> QuickStackRadius;

		internal static SyncedEntry<bool> LockedSlotsEnabled;

		internal static SyncedEntry<bool> LockedChestSlotsEnabled;

		internal static SyncedEntry<bool> SortEnabled;

		internal static SyncedEntry<bool> RestockEnabled;

		internal static SyncedEntry<float> RestockRadius;

		internal static SyncedEntry<float> WeightMultiplier;

		internal static SyncedEntry<bool> ChestPeekEnabled;

		internal static SyncedEntry<bool> ChestLabelsEnabled;

		internal static ConfigEntry<bool> ShowSortButton;

		internal static ConfigEntry<bool> ShowQuickStackButton;

		internal static ConfigEntry<bool> ShowRestockButton;

		internal static ConfigEntry<bool> ShowCraftToggleButton;

		internal static ConfigEntry<KeyboardShortcut> QuickStackKey;

		internal static ConfigEntry<KeyboardShortcut> RestockKey;

		internal static ConfigEntry<KeyboardShortcut> ToggleCraftFromChestsKey;

		internal static ConfigEntry<KeyboardShortcut> SortKey;

		internal static ConfigEntry<bool> LockSlotMiddleClick;

		internal static ConfigEntry<KeyboardShortcut> LockSlotHoldKey;

		internal static ConfigEntry<SortMode> CurrentSortMode;

		internal static ConfigEntry<bool> ShowChestCountTooltip;

		internal static ConfigEntry<bool> RestockEquippedAmmo;

		internal static ConfigEntry<int> RestockAmmoTarget;

		internal static ConfigEntry<string> RestockItems;

		internal static ConfigEntry<bool> ShowChestPeek;

		internal static ConfigEntry<ChestPeekStyle> ChestPeekStyle;

		internal static ConfigEntry<int> ChestPeekItems;

		internal static ConfigEntry<int> ChestLabelSize;

		internal static ConfigEntry<bool> ShowHaveCount;

		internal static ConfigEntry<bool> ShowHaveCountBuilding;

		internal static ConfigEntry<RecipeSortMode> RecipeSortMode;

		internal static ConfigEntry<bool> ShowRecipeSortButton;

		internal static ConfigEntry<bool> ShowChestSearch;

		internal static ConfigEntry<bool> ShowRecipeSearch;

		internal static void Bind(ConfigFile file)
		{
			//IL_03f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_040f: Unknown result type (might be due to invalid IL or missing references)
			//IL_042e: Unknown result type (might be due to invalid IL or missing references)
			//IL_044d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0491: Unknown result type (might be due to invalid IL or missing references)
			AcceptableValueRange<int> acceptable = new AcceptableValueRange<int>(1, 100);
			AcceptableValueRange<int> acceptable2 = new AcceptableValueRange<int>(0, 100);
			AcceptableValueRange<float> acceptable3 = new AcceptableValueRange<float>(1f, 100f);
			StacksEnabled = ModConfig.BindServer("Stacks", "Enabled", defaultValue: true, "Bigger stack sizes.");
			StackMultiplier = ModConfig.BindServer("Stacks", "StackMultiplier", 5, "Multiplies the max stack of every item that stacks.\nItems that don't stack (weapons, armor, tools, Megingjord, Wisplight, ...) never change.\nItemOverrides and the category multipliers below win over this one.", (AcceptableValueBase)(object)acceptable);
			MaterialMultiplier = ModConfig.BindServer("Stacks", "MaterialMultiplier", 0, "Stack multiplier for materials (wood, ore, hides, ...). 0 = same as StackMultiplier.", (AcceptableValueBase)(object)acceptable2);
			ConsumableMultiplier = ModConfig.BindServer("Stacks", "ConsumableMultiplier", 0, "Stack multiplier for food, potions and meads. 0 = same as StackMultiplier.", (AcceptableValueBase)(object)acceptable2);
			AmmoMultiplier = ModConfig.BindServer("Stacks", "AmmoMultiplier", 0, "Stack multiplier for arrows, bolts and missiles. 0 = same as StackMultiplier.", (AcceptableValueBase)(object)acceptable2);
			TrophyMultiplier = ModConfig.BindServer("Stacks", "TrophyMultiplier", 0, "Stack multiplier for trophies. 0 = same as StackMultiplier.", (AcceptableValueBase)(object)acceptable2);
			ItemOverrides = ModConfig.BindServer("Stacks", "ItemOverrides", "", "Exact max stack for single items, by prefab name, comma separated. Example: Coins:5000, Wood:1000\nItems that don't stack are skipped.");
			ExcludedItems = ModConfig.BindServer("Stacks", "ExcludedItems", "", "Items that keep their vanilla stack size, by prefab name, comma separated. Example: Coins, Amber");
			SharedChestsEnabled = ModConfig.BindServer("Shared Chests", "Enabled", defaultValue: true, "Several players can use the same chest at the same time. Carts, ships and graves stay vanilla.");
			ShowViewersInHoverText = ModConfig.BindServer("Shared Chests", "ShowViewersInHoverText", defaultValue: true, "Hovering a chest shows who has it open.");
			IgnorePersonalChestLock = ModConfig.BindServer("Shared Chests", "IgnorePersonalChestLock", defaultValue: true, "Everyone can open personal (private) chests, not only the player who built them.");
			IgnoreWardOnChests = ModConfig.BindServer("Shared Chests", "IgnoreWardOnChests", defaultValue: true, "Wards don't stop anyone from opening chests.");
			CraftFromChestsEnabled = ModConfig.BindServer("Craft From Chests", "Enabled", defaultValue: true, "Crafting, building, cooking, smelting and refueling use items from nearby chests.");
			CraftingRadius = ModConfig.BindServer("Craft From Chests", "CraftingRadius", 20f, "Chests within this many meters of a crafting station (workbench, forge, ...) are used.", (AcceptableValueBase)(object)acceptable3);
			BuildingRadius = ModConfig.BindServer("Craft From Chests", "BuildingRadius", 20f, "Chests within this many meters of you are used when building with the hammer, hoe or cultivator.", (AcceptableValueBase)(object)acceptable3);
			StationRadius = ModConfig.BindServer("Craft From Chests", "StationRadius", 20f, "Chests within this many meters of a smelter, kiln, cooking station, fire, fermenter, turret and so on are used.", (AcceptableValueBase)(object)acceptable3);
			InventoryFirst = ModConfig.BindServer("Craft From Chests", "InventoryFirst", defaultValue: true, "Use items in your inventory before taking from chests.");
			QuickStackEnabled = ModConfig.BindServer("Quick Stack", "Enabled", defaultValue: true, "Quick-stack button: puts your items into nearby chests that already have the same item.");
			QuickStackRadius = ModConfig.BindServer("Quick Stack", "Radius", 20f, "Chests within this many meters of you are used.", (AcceptableValueBase)(object)acceptable3);
			LockedSlotsEnabled = ModConfig.BindServer("Quick Stack", "LockedSlotsEnabled", defaultValue: true, "Players can lock inventory slots (middle-click a slot). Quick stack, Place stacks and Sort leave locked slots alone.");
			LockedChestSlotsEnabled = ModConfig.BindServer("Quick Stack", "LockedChestSlotsEnabled", defaultValue: true, "Players can lock slots in player-built chests. Crafting, building, stations, Restock, Take all and Sort leave them alone. Adding items and moving them by hand still work. Off = locks are ignored but kept in the world.");
			SortEnabled = ModConfig.BindServer("Sort", "Enabled", defaultValue: true, "Sort button for your inventory and chests.");
			RestockEnabled = ModConfig.BindServer("Restock", "Enabled", defaultValue: true, "Restock button: tops up your ammo and your restock list from nearby chests.");
			RestockRadius = ModConfig.BindServer("Restock", "Radius", 20f, "Chests within this many meters of you are used.", (AcceptableValueBase)(object)acceptable3);
			WeightMultiplier = ModConfig.BindServer("Difficulty", "WeightMultiplier", 1f, "How much carried items weigh. 1.0 = vanilla, 0.5 = half.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f));
			ChestPeekEnabled = ModConfig.BindServer("Chests", "ChestPeekEnabled", defaultValue: true, "Hovering a chest shows what's inside. Carts, ships and graves stay vanilla.");
			ChestLabelsEnabled = ModConfig.BindServer("Chests", "ChestLabelsEnabled", defaultValue: true, "Chests can be renamed and given a name color (Rename and Color buttons in the chest window). Off = names are hidden and the buttons are gone; saved names stay in the world.");
			ShowSortButton = ModConfig.BindPersonal("Buttons", "ShowSortButton", defaultValue: true, "Show the Sort button.");
			ShowQuickStackButton = ModConfig.BindPersonal("Buttons", "ShowQuickStackButton", defaultValue: true, "Show the Quick-stack button.");
			ShowRestockButton = ModConfig.BindPersonal("Buttons", "ShowRestockButton", defaultValue: true, "Show the Restock button.");
			ShowCraftToggleButton = ModConfig.BindPersonal("Buttons", "ShowCraftToggleButton", defaultValue: true, "Show the button that switches crafting from chests on and off for you.");
			ShowRecipeSortButton = ModConfig.BindPersonal("Buttons", "ShowRecipeSortButton", defaultValue: true, "Show the Sort button in the crafting window (workbench, forge, ...).");
			ShowChestSearch = ModConfig.BindPersonal("Buttons", "ShowChestSearch", defaultValue: true, "Show a search box beside the chest window. Slots whose item doesn't match go dim.");
			ShowRecipeSearch = ModConfig.BindPersonal("Buttons", "ShowRecipeSearch", defaultValue: true, "Show a search box in the crafting window. The list shows recipes whose name matches, then recipes that use a matching ingredient.");
			QuickStackKey = ModConfig.BindPersonal<KeyboardShortcut>("Hotkeys", "QuickStackKey", KeyboardShortcut.Empty, "Puts your items into nearby chests, like the Quick-stack button. Empty = no key.");
			RestockKey = ModConfig.BindPersonal<KeyboardShortcut>("Hotkeys", "RestockKey", KeyboardShortcut.Empty, "Tops up from nearby chests, like the Restock button. Empty = no key.");
			ToggleCraftFromChestsKey = ModConfig.BindPersonal<KeyboardShortcut>("Hotkeys", "ToggleCraftFromChestsKey", KeyboardShortcut.Empty, "Switches crafting from chests on and off. Empty = no key.");
			SortKey = ModConfig.BindPersonal<KeyboardShortcut>("Hotkeys", "SortKey", KeyboardShortcut.Empty, "Sorts your inventory, like the Sort button. Empty = no key.");
			LockSlotMiddleClick = ModConfig.BindPersonal("Hotkeys", "LockSlotMiddleClick", defaultValue: true, "Middle-click (press the mouse wheel) on an inventory slot to lock or unlock it.");
			LockSlotHoldKey = ModConfig.BindPersonal<KeyboardShortcut>("Hotkeys", "LockSlotHoldKey", new KeyboardShortcut((KeyCode)285, Array.Empty<KeyCode>()), "Hold this key and left-click an inventory slot to lock or unlock it (for mice without a wheel button). Empty = off.");
			CurrentSortMode = ModConfig.BindPersonal("Options", "SortMode", SortMode.Name, "How the Sort button sorts your inventory: by name, type or weight. Right-clicking the Sort button changes it too.");
			RecipeSortMode = ModConfig.BindPersonal("Options", "RecipeSortMode", VikingQoL.Modules.Storage.RecipeSortMode.Name, "How the recipe list in crafting windows is sorted: Original (the game's order), Name, Type or Weight. Recipes you can make always come first. The Sort button in the crafting window changes it too.");
			ShowHaveCount = ModConfig.BindPersonal("Options", "ShowHaveCount", defaultValue: true, "Crafting window: under each ingredient, show how many you have (inventory and nearby chests) and how many you need, like 100/6.");
			ShowHaveCountBuilding = ModConfig.BindPersonal("Options", "ShowHaveCountBuilding", defaultValue: true, "The same counts in the building menu (hammer, hoe, cultivator).");
			ShowChestCountTooltip = ModConfig.BindPersonal("Options", "ShowChestCountTooltip", defaultValue: true, "Item tooltips show how many are in nearby chests.");
			RestockEquippedAmmo = ModConfig.BindPersonal("Options", "RestockEquippedAmmo", defaultValue: true, "Restock tops up the arrows or bolts you have equipped.");
			RestockAmmoTarget = ModConfig.BindPersonal("Options", "RestockAmmoTarget", 100, "Restock fills your equipped ammo up to this many.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10000));
			RestockItems = ModConfig.BindPersonal("Options", "RestockItems", "", "Items Restock keeps topped up, as Name:Amount, comma separated. Example: MeadHealthMinor:10, CookedMeat:20");
			ShowChestPeek = ModConfig.BindPersonal("Chests", "ShowChestPeek", defaultValue: true, "Show what's inside when you hover a chest.");
			ChestPeekStyle = ModConfig.BindPersonal("Chests", "ChestPeekStyle", VikingQoL.Modules.Storage.ChestPeekStyle.Icons, "How the chest hover shows what's inside: Icons (item pictures with totals) or Text (a grey list).");
			ChestPeekItems = ModConfig.BindPersonal("Chests", "ChestPeekItems", 6, "How many different items the chest hover shows, biggest stacks first.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 15));
			ChestLabelSize = ModConfig.BindPersonal("Chests", "ChestLabelSize", 120, "Size of a chest's name in the hover text, in percent. 100 = same size as the rest of the text.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(100, 150));
		}
	}
}
namespace VikingQoL.Modules.Storage.Shared
{
	internal static class ChestActions
	{
		private static Player LocalPlayer => Player.m_localPlayer;

		internal static void MoveWithin(Container chest, ItemData item, int amount, Vector2i to)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			ContainerService.Move(chest, item, amount, to, delegate(OpStatus status, Inventory _)
			{
				Report(status);
			});
		}

		internal static void TakeToPlayer(Container chest, ItemData item, int amount, Vector2i? target)
		{
			//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)
			Vector2i source = item.m_gridPos;
			bool wholeStack = amount >= item.m_stack;
			ContainerService.Take(chest, new TakeItem[1] { TakeItem.From(item, amount) }, delegate(OpStatus status, Inventory taken)
			{
				//IL_0021: 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)
				Report(status);
				if (target.HasValue)
				{
					PlaceAtTarget(chest, taken, target.Value, source, wholeStack);
				}
				GiveToPlayer(chest, taken);
			}, respectLocks: false);
		}

		internal static void PutFromPlayer(Container chest, ItemData item, int amount, Vector2i? target)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = LocalPlayer;
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			((Humanoid)localPlayer).RemoveEquipAction(item);
			((Humanoid)localPlayer).UnequipItem(item, false);
			Vector2i source = item.m_gridPos;
			amount = Mathf.Min(amount, item.m_stack);
			bool allowSwap = amount == item.m_stack;
			Inventory val = ContainerService.NewTemp();
			val.MoveItemToThis(inventory, item, amount, source.x, source.y);
			ContainerService.Put(chest, val, target, allowSwap, delegate(OpStatus status, Inventory leftovers)
			{
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				Report(status);
				ReturnToPlayer(chest, leftovers, source);
			});
		}

		internal static void TakeAndDrop(Container chest, ItemData item, int amount)
		{
			ContainerService.Take(chest, new TakeItem[1] { TakeItem.From(item, amount) }, delegate(OpStatus status, Inventory taken)
			{
				Report(status);
				DropAll(taken);
			}, respectLocks: false);
		}

		internal static void TakeAll(Container chest)
		{
			Inventory val = CopyOf(((Humanoid)LocalPlayer).GetInventory());
			List<TakeItem> list = new List<TakeItem>();
			foreach (ItemData allItem in chest.GetInventory().GetAllItems())
			{
				if (!ChestLocks.IsLocked(chest, allItem))
				{
					ItemData val2 = allItem.Clone();
					int stack = val2.m_stack;
					int num = (val.AddItem(val2) ? stack : (stack - val2.m_stack));
					if (num > 0)
					{
						list.Add(TakeItem.From(allItem, num));
					}
				}
			}
			if (list.Count != 0)
			{
				ContainerService.Take(chest, list, delegate(OpStatus status, Inventory taken)
				{
					Report(status);
					GiveToPlayer(chest, taken);
				});
			}
		}

		internal static void PlaceStacks(Container chest)
		{
			Player localPlayer = LocalPlayer;
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			Inventory inventory2 = chest.GetInventory();
			Inventory val = ContainerService.NewTemp();
			foreach (ItemData item in inventory.GetAllItems().ToList())
			{
				if (inventory2.ContainsItemByName(item.m_shared.m_name) && !((Humanoid)localPlayer).IsItemEquiped(item) && !LockedSlots.IsLocked(item))
				{
					val.MoveItemToThis(inventory, item, item.m_stack, item.m_gridPos.x, item.m_gridPos.y);
				}
			}
			int sent = val.CountItems((string)null, -1, false);
			if (sent == 0)
			{
				((Character)localPlayer).Message((MessageType)2, "$msg_stackall_none", 0, (Sprite)null, false);
				return;
			}
			ContainerService.StackInto(chest, val, delegate(OpStatus status, Inventory leftovers)
			{
				int num = sent - leftovers.CountItems((string)null, -1, false);
				ReturnToPlayer(chest, leftovers, null);
				if (Report(status))
				{
					Player localPlayer2 = LocalPlayer;
					if (localPlayer2 != null)
					{
						((Character)localPlayer2).Message((MessageType)2, (num > 0) ? ("$msg_stackall " + num) : "$msg_stackall_none", 0, (Sprite)null, false);
					}
				}
			});
		}

		private static void PlaceAtTarget(Container chest, Inventory taken, Vector2i target, Vector2i chestSource, bool wholeStack)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = LocalPlayer;
			Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
			ItemData val2 = taken.GetAllItems().FirstOrDefault();
			if (val == null || val2 == null)
			{
				return;
			}
			ItemData itemAt = val.GetItemAt(target.x, target.y);
			if (itemAt == null || (!(itemAt.m_shared.m_name != val2.m_shared.m_name) && (val2.m_shared.m_maxQuality <= 1 || itemAt.m_quality == val2.m_quality) && itemAt.m_shared.m_maxStackSize != 1))
			{
				val.MoveItemToThis(taken, val2, val2.m_stack, target.x, target.y);
			}
			else if (wholeStack && !itemAt.m_shared.m_questItem)
			{
				((Humanoid)localPlayer).RemoveEquipAction(itemAt);
				((Humanoid)localPlayer).UnequipItem(itemAt, false);
				Inventory val3 = ContainerService.NewTemp();
				val3.MoveItemToThis(val, itemAt, itemAt.m_stack, 0, 0);
				val.MoveItemToThis(taken, val2, val2.m_stack, target.x, target.y);
				ContainerService.Put(chest, val3, chestSource, allowSwap: false, delegate(OpStatus status, Inventory leftovers)
				{
					//IL_000f: Unknown result type (might be due to invalid IL or missing references)
					Report(status);
					ReturnToPlayer(chest, leftovers, target);
				});
			}
		}

		internal static void GiveToPlayer(Container chest, Inventory items)
		{
			Player localPlayer = LocalPlayer;
			Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
			if (val != null)
			{
				foreach (ItemData item in items.GetAllItems().ToList())
				{
					val.MoveItemToThis(items, item);
				}
			}
			PutBackOrDrop(chest, items);
		}

		internal static void ReturnToPlayer(Container chest, Inventory items, Vector2i? preferred)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = LocalPlayer;
			Inventory val = ((localPlayer != null) ? ((Humanoid)localPlayer).GetInventory() : null);
			if (val != null)
			{
				foreach (ItemData item in items.GetAllItems().ToList())
				{
					if ((!preferred.HasValue || !TryMoveTo(val, items, item, preferred.Value)) && !TryMoveTo(val, items, item, item.m_gridPos))
					{
						val.MoveItemToThis(items, item);
					}
				}
			}
			if (items.NrOfItems() > 0)
			{
				DropAll(items);
			}
		}

		private static bool TryMoveTo(Inventory inventory, Inventory from, ItemData item, Vector2i pos)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			if (pos.x < 0 || pos.y < 0 || pos.x >= inventory.GetWidth() || pos.y >= inventory.GetHeight())
			{
				return false;
			}
			inventory.MoveItemToThis(from, item, item.m_stack, pos.x, pos.y);
			return !from.ContainsItem(item);
		}

		private static void PutBackOrDrop(Container chest, Inventory items)
		{
			if (items.NrOfItems() == 0)
			{
				return;
			}
			if ((Object)(object)chest == (Object)null || !chest.m_nview.IsValid())
			{
				DropAll(items);
				return;
			}
			ContainerService.Put(chest, items, null, allowSwap: false, delegate(OpStatus status, Inventory leftovers)
			{
				DropAll(leftovers);
			});
		}

		private static void DropAll(Inventory items)
		{
			Player localPlayer = LocalPlayer;
			if ((Object)(object)localPlayer == (Object)null || items.NrOfItems() == 0)
			{
				return;
			}
			foreach (ItemData item in items.GetAllItems().ToList())
			{
				((Humanoid)localPlayer).DropItem(items, item, item.m_stack);
			}
			((Character)localPlayer).Message((MessageType)2, "$inventory_full", 0, (Sprite)null, false);
		}

		private static Inventory CopyOf(Inventory source)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_0029: Expected O, but got Unknown
			Inventory val = new Inventory("VikingQoL", (Sprite)null, source.GetWidth(), source.GetHeight());
			val.Load(new ZPackage(ContainerService.Pack(source)));
			return val;
		}

		private static bool Report(OpStatus status)
		{
			switch (status)
			{
			case OpStatus.Ok:
				return true;
			case OpStatus.Invalid:
			{
				Player localPlayer2 = LocalPlayer;
				if (localPlayer2 != null)
				{
					((Character)localPlayer2).Message((MessageType)2, "Something went wrong with the chest; see the log.", 0, (Sprite)null, false);
				}
				return true;
			}
			default:
			{
				Player localPlayer = LocalPlayer;
				if (localPlayer != null)
				{
					((Character)localPlayer).Message((MessageType)2, "The chest didn't respond, try again.", 0, (Sprite)null, false);
				}
				return false;
			}
			}
		}
	}
	[HarmonyPatch]
	internal static class ChestCrafting
	{
		private sealed class Pending
		{
			public Container Chest;

			public bool Disabled;

			public long PlayerId;

			public float SentAt;

			public int Retries;
		}

		private const string Key = "vqol_nocraft";

		private const string RpcSet = "VikingQoL_SetChestCrafting";

		private const float ConfirmAfter = 1.5f;

		private const int MaxRetries = 2;

		private static readonly int KeyHash = StringExtensionMethods.GetStableHashCode("vqol_nocraft");

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

		[HarmonyPatch(typeof(Container), "Awake")]
		[HarmonyPostfix]
		private static void ContainerAwake(Container __instance)
		{
			if (!((Object)(object)__instance.m_nview == (Object)null) && __instance.m_nview.IsValid())
			{
				Container chest = __instance;
				chest.m_nview.Register<bool, long>("VikingQoL_SetChestCrafting", (Action<long, bool, long>)delegate(long sender, bool disabled, long playerId)
				{
					Apply(chest, disabled, playerId);
				});
			}
		}

		internal static bool IsDisabled(Container chest)
		{
			if (ChestRules.IsPlayerChest(chest))
			{
				return chest.m_nview.GetZDO().GetString(KeyHash, "") == "1";
			}
			return false;
		}

		internal static bool EffectiveDisabled(Container chest)
		{
			return PendingChanges.FirstOrDefault((Pending p) => (Object)(object)p.Chest == (Object)(object)chest)?.Disabled ?? IsDisabled(chest);
		}

		internal static bool CanSet(Container chest)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			if (StorageConfig.CraftFromChestsEnabled.Value && ChestRules.IsPlayerChest(chest) && (Object)(object)Game.instance != (Object)null && chest.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID()))
			{
				if (chest.m_checkGuardStone && !StorageConfig.IgnoreWardOnChests.Value)
				{
					return PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, false);
				}
				return true;
			}
			return false;
		}

		internal static void Toggle(Container chest)
		{
			if (CanSet(chest))
			{
				Pending pending = new Pending
				{
					Chest = chest,
					Disabled = !EffectiveDisabled(chest),
					PlayerId = Game.instance.GetPlayerProfile().GetPlayerID(),
					SentAt = Time.unscaledTime
				};
				PendingChanges.RemoveAll((Pending p) => (Object)(object)p.Chest == (Object)(object)chest);
				PendingChanges.Add(pending);
				Send(pending);
			}
		}

		private static void Apply(Container chest, bool disabled, long playerId)
		{
			if (ChestRules.IsPlayerChest(chest) && chest.m_nview.IsOwner() && StorageConfig.CraftFromChestsEnabled.Value && chest.CheckAccess(playerId))
			{
				string text = (disabled ? "1" : "");
				ZDO zDO = chest.m_nview.GetZDO();
				if (zDO.GetString(KeyHash, "") != text)
				{
					zDO.Set("vqol_nocraft", text);
				}
			}
		}

		private static void Send(Pending pending)
		{
			ZNetView nview = pending.Chest.m_nview;
			if (!nview.HasOwner())
			{
				nview.ClaimOwnership();
			}
			if (nview.IsOwner())
			{
				Apply(pending.Chest, pending.Disabled, pending.PlayerId);
				return;
			}
			nview.InvokeRPC("VikingQoL_SetChestCrafting", new object[2] { pending.Disabled, pending.PlayerId });
		}

		internal static void Tick()
		{
			if (PendingChanges.Count == 0)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			foreach (Pending item in PendingChanges.ToList())
			{
				if (unscaledTime - item.SentAt < 1.5f)
				{
					continue;
				}
				if (!ChestRules.IsPlayerChest(item.Chest) || IsDisabled(item.Chest) == item.Disabled || !StorageConfig.CraftFromChestsEnabled.Value)
				{
					PendingChanges.Remove(item);
				}
				else if (item.Retries >= 2)
				{
					PendingChanges.Remove(item);
					Player localPlayer = Player.m_localPlayer;
					if (localPlayer != null)
					{
						((Character)localPlayer).Message((MessageType)2, "The chest didn't respond, try again.", 0, (Sprite)null, false);
					}
				}
				else
				{
					item.Retries++;
					item.SentAt = unscaledTime;
					Log.Debug($"Chest crafting switch not confirmed, asking again (try {item.Retries}).");
					Send(item);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class ChestHover
	{
		private static bool _failed;

		[HarmonyPatch(typeof(Container), "GetHoverText")]
		[HarmonyPostfix]
		private static void Hover(Container __instance, ref string __result)
		{
			try
			{
				string text = ChestLabels.Label(__instance);
				string text2 = ChestViewers.OpenByLine(__instance);
				string text3 = PeekBlock(__instance);
				string text4 = (ChestCrafting.IsDisabled(__instance) ? "<color=#AAAAAA>Not used for crafting</color>" : null);
				if (text != null || text2 != null || text3 != null || text4 != null)
				{
					int num = __result.IndexOf('\n');
					string text5 = ((num < 0) ? __result : __result.Substring(0, num));
					string text6 = ((num < 0) ? "" : __result.Substring(num));
					if (text != null)
					{
						text5 = WithLabel(text, ChestLabels.ColorHex(__instance), text5, Localization.instance.Localize(__instance.m_name));
					}
					__result = text5 + ((text2 != null) ? ("\n" + text2) : "") + text6 + ((text4 != null) ? ("\n" + text4) : "") + ((text3 != null) ? ("\n" + text3) : "");
				}
			}
			catch (Exception ex)
			{
				if (!_failed)
				{
					Log.Warn("Chest hover text failed: " + ex.Message);
				}
				_failed = true;
			}
		}

		private static string WithLabel(string label, string colorHex, string nameLine, string chestName)
		{
			string text = ((colorHex != null) ? ("<color=" + colorHex + ">" + label + "</color>") : label);
			int value = StorageConfig.ChestLabelSize.Value;
			if (value != 100)
			{
				text = $"<size={value}%>{text}</size>";
			}
			if (!nameLine.StartsWith(chestName, StringComparison.Ordinal))
			{
				return text + " (" + nameLine + ")";
			}
			return text + " (" + chestName + ")" + nameLine.Substring(chestName.Length);
		}

		private static string PeekBlock(Container chest)
		{
			if (StorageConfig.ChestPeekStyle.Value == ChestPeekStyle.Icons && ChestPeekIcons.Working)
			{
				return null;
			}
			ChestPeekData.Result result = ChestPeekData.Get(chest);
			if (result == null)
			{
				return null;
			}
			return ChestPeekData.TextOf(result);
		}
	}
	[HarmonyPatch]
	internal static class ChestLabels
	{
		private sealed class Pending
		{
			public Container Chest;

			public bool IsColor;

			public string Value;

			public float SentAt;

			public int Retries;
		}

		private const string Key = "vqol_label";

		private const string ColorKey = "vqol_label_color";

		private const string RpcSetLabel = "VikingQoL_SetLabel";

		private const string RpcSetColor = "VikingQoL_SetLabelColor";

		private const int MaxLength = 32;

		private const float ConfirmAfter = 1.5f;

		private const int MaxRetries = 2;

		private static readonly int KeyHash = StringExtensionMethods.GetStableHashCode("vqol_label");

		private static readonly int ColorKeyHash = StringExtensionMethods.GetStableHashCode("vqol_label_color");

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

		[HarmonyPatch(typeof(Container), "Awake")]
		[HarmonyPostfix]
		private static void ContainerAwake(Container __instance)
		{
			ZNetView nview = __instance.m_nview;
			if (!((Object)(object)nview == (Object)null) && nview.GetZDO() != null)
			{
				Container chest = __instance;
				nview.Register<string>("VikingQoL_SetLabel", (Action<long, string>)delegate(long sender, string text)
				{
					Apply(chest, text);
				});
				nview.Register<string>("VikingQoL_SetLabelColor", (Action<long, string>)delegate(long sender, string name)
				{
					ApplyColor(chest, name);
				});
			}
		}

		internal static string Label(Container chest)
		{
			if (!StorageConfig.ChestLabelsEnabled.Value || (Object)(object)chest == (Object)null || (Object)(object)chest.m_nview == (Object)null || !chest.m_nview.IsValid())
			{
				return null;
			}
			string text = chest.m_nview.GetZDO().GetString(KeyHash, "");
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return null;
		}

		internal static string ColorName(Container chest)
		{
			if (Label(chest) == null)
			{
				return null;
			}
			if (!Palette.TryGet(chest.m_nview.GetZDO().GetString(ColorKeyHash, ""), out var name, out var _))
			{
				return null;
			}
			return name;
		}

		internal static string ColorHex(Container chest)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (!Palette.TryGet(ColorName(chest), out var _, out var color))
			{
				return null;
			}
			return Palette.ToHex(color);
		}

		internal static string EffectiveColorName(Container chest)
		{
			foreach (Pending pendingChange in PendingChanges)
			{
				if (pendingChange.IsColor && (Object)(object)pendingChange.Chest == (Object)(object)chest)
				{
					return (pendingChange.Value.Length == 0) ? null : pendingChange.Value;
				}
			}
			return ColorName(chest);
		}

		internal static bool CanRename(Container chest)
		{
			if (StorageConfig.ChestLabelsEnabled.Value && ChestRules.IsPlayerChest(chest))
			{
				return chest.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID());
			}
			return false;
		}

		internal static string Sanitize(string text)
		{
			text = (text ?? "").Replace("<", "").Replace(">", "").Replace("\n", " ")
				.Trim();
			if (text.Length <= 32)
			{
				return text;
			}
			return text.Substring(0, 32).TrimEnd();
		}

		private static void Apply(Container chest, string text)
		{
			if (chest.m_nview.IsValid() && chest.m_nview.IsOwner() && StorageConfig.ChestLabelsEnabled.Value && ChestRules.IsPlayerChest(chest))
			{
				string text2 = Sanitize(text);
				ZDO zDO = chest.m_nview.GetZDO();
				if (zDO.GetString(KeyHash, "") != text2)
				{
					zDO.Set("vqol_label", text2);
				}
				if (text2.Length == 0 && zDO.GetString(ColorKeyHash, "").Length > 0)
				{
					zDO.Set("vqol_label_color", "");
				}
			}
		}

		private static void ApplyColor(Container chest, string name)
		{
			if (chest.m_nview.IsValid() && chest.m_nview.IsOwner() && StorageConfig.ChestLabelsEnabled.Value && ChestRules.IsPlayerChest(chest))
			{
				ZDO zDO = chest.m_nview.GetZDO();
				string name2 = "";
				if ((string.IsNullOrWhiteSpace(name) || (Palette.TryGet(name, out name2, out var _) && zDO.GetString(KeyHash, "").Length != 0)) && zDO.GetString(ColorKeyHash, "") != name2)
				{
					zDO.Set("vqol_label_color", name2);
				}
			}
		}

		internal static void Set(Container chest, string text)
		{
			if (!((Object)(object)chest == (Object)null) && chest.m_nview.IsValid())
			{
				string value = Sanitize(text);
				Track(new Pending
				{
					Chest = chest,
					Value = value,
					SentAt = Time.unscaledTime
				});
				Send(chest, value, isColor: false);
			}
		}

		internal static void SetColor(Container chest, string colorName)
		{
			if (!((Object)(object)chest == (Object)null) && chest.m_nview.IsValid())
			{
				string value = colorName ?? "";
				Track(new Pending
				{
					Chest = chest,
					IsColor = true,
					Value = value,
					SentAt = Time.unscaledTime
				});
				Send(chest, value, isColor: true);
			}
		}

		internal static void CycleColor(Container chest, int direction)
		{
			if (Label(chest) == null)
			{
				return;
			}
			string current = EffectiveColorName(chest);
			int num = Palette.Entries.Length + 1;
			int num2 = 0;
			if (current != null)
			{
				num2 = Array.FindIndex(Palette.Entries, ((string name, Color color) e) => e.name == current) + 1;
			}
			num2 = ((num2 + direction) % num + num) % num;
			SetColor(chest, (num2 == 0) ? "" : Palette.Entries[num2 - 1].name);
		}

		private static void Track(Pending change)
		{
			PendingChanges.RemoveAll((Pending p) => (Object)(object)p.Chest == (Object)(object)change.Chest && p.IsColor == change.IsColor);
			PendingChanges.Add(change);
		}

		private static void Send(Container chest, string value, bool isColor)
		{
			if (!chest.m_nview.HasOwner())
			{
				chest.m_nview.ClaimOwnership();
			}
			if (chest.m_nview.IsOwner())
			{
				if (isColor)
				{
					ApplyColor(chest, value);
				}
				else
				{
					Apply(chest, value);
				}
			}
			else
			{
				chest.m_nview.InvokeRPC(isColor ? "VikingQoL_SetLabelColor" : "VikingQoL_SetLabel", new object[1] { value });
			}
		}

		internal static void Tick()
		{
			if (PendingChanges.Count == 0)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			foreach (Pending item in PendingChanges.ToList())
			{
				if (unscaledTime - item.SentAt < 1.5f)
				{
					continue;
				}
				if ((Object)(object)item.Chest == (Object)null || !item.Chest.m_nview.IsValid() || Confirmed(item))
				{
					PendingChanges.Remove(item);
				}
				else if (item.Retries >= 2)
				{
					PendingChanges.Remove(item);
					Player localPlayer = Player.m_localPlayer;
					if (localPlayer != null)
					{
						((Character)localPlayer).Message((MessageType)2, "The chest didn't respond, try again.", 0, (Sprite)null, false);
					}
				}
				else
				{
					item.Retries++;
					item.SentAt = unscaledTime;
					Log.Debug(string.Format("Chest {0} not confirmed, asking again (try {1}).", item.IsColor ? "label color" : "label", item.Retries));
					Send(item.Chest, item.Value, item.IsColor);
				}
			}
		}

		private static bool Confirmed(Pending pending)
		{
			ZDO zDO = pending.Chest.m_nview.GetZDO();
			if (!pending.IsColor)
			{
				return (zDO.GetString(KeyHash, "") ?? "") == pending.Value;
			}
			if (!((zDO.GetString(ColorKeyHash, "") ?? "") == pending.Value))
			{
				return (zDO.GetString(KeyHash, "") ?? "").Length == 0;
			}
			return true;
		}
	}
	internal sealed class ChestLabelReceiver : TextReceiver
	{
		private readonly Container _chest;

		private ChestLabelReceiver(Container chest)
		{
			_chest = chest;
		}

		public string GetText()
		{
			return ChestLabels.Label(_chest) ?? "";
		}

		public void SetText(string text)
		{
			ChestLabels.Set(_chest, text);
		}

		internal static void Begin(Container chest)
		{
			if (!((Object)(object)chest == (Object)null) && !((Object)(object)TextInput.instance == (Object)null) && ChestLabels.CanRename(chest))
			{
				InventoryGui.instance.Hide();
				TextInput.instance.RequestText((TextReceiver)(object)new ChestLabelReceiver(chest), "Chest name", 32);
			}
		}
	}
	[HarmonyPatch]
	internal static class ChestLabelsUI
	{
		private static Color? _defaultColor;

		[HarmonyPatch(typeof(InventoryGui), "UpdateContainer")]
		[HarmonyPostfix]
		private static void UpdateContainer(InventoryGui __instance)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: 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_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			Container currentContainer = __instance.m_currentContainer;
			if (!((Object)(object)currentContainer == (Object)null) && ((Component)__instance.m_container).gameObject.activeSelf)
			{
				TMP_Text containerName = __instance.m_containerName;
				if (!_defaultColor.HasValue)
				{
					_defaultColor = ((Graphic)containerName).color;
				}
				string text = ChestLabels.Label(currentContainer);
				if (text != null && containerName.text != text)
				{
					containerName.text = text;
				}
				Color value = _defaultColor.Value;
				string text2 = ((text != null) ? ChestLabels.ColorHex(currentContainer) : null);
				if (text2 != null)
				{
					ColorUtility.TryParseHtmlString(text2, ref value);
				}
				if (((Graphic)containerName).color != value)
				{
					((Graphic)containerName).color = value;
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class ChestLocks
	{
		private sealed class Snapshot
		{
			public string Text;

			public readonly HashSet<Vector2i> Slots = new HashSet<Vector2i>();
		}

		private sealed class Pending
		{
			public Container Chest;

			public Vector2i Position;

			public bool Locked;

			public long PlayerId;

			public float SentAt;

			public int Retries;
		}

		private const string Key = "vqol_locks";

		private const string RpcSet = "VikingQoL_SetChestLock";

		private const float ConfirmAfter = 1.5f;

		private const int MaxRetries = 2;

		private static readonly int KeyHash = StringExtensionMethods.GetStableHashCode("vqol_locks");

		private static readonly ConditionalWeakTable<Container, Snapshot> Snapshots = new ConditionalWeakTable<Container, Snapshot>();

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

		internal static bool Enabled => StorageConfig.LockedChestSlotsEnabled.Value;

		internal static int Revision { get; private set; }

		[HarmonyPatch(typeof(Container), "Awake")]
		[HarmonyPostfix]
		private static void ContainerAwake(Container __instance)
		{
			if (!((Object)(object)__instance.m_nview == (Object)null) && __instance.m_nview.IsValid())
			{
				Container chest = __instance;
				chest.m_nview.Register<int, int, bool, long>("VikingQoL_SetChestLock", (Method<int, int, bool, long>)delegate(long sender, int x, int y, bool locked, long playerId)
				{
					//IL_0008: Unknown result type (might be due to invalid IL or missing references)
					Apply(chest, new Vector2i(x, y), locked, playerId);
				});
			}
		}

		internal static string State(Container chest)
		{
			if (!Enabled || !ChestRules.IsPlayerChest(chest))
			{
				return "";
			}
			return Read(chest).Text;
		}

		private static Snapshot Read(Container chest)
		{
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			string text = chest.m_nview.GetZDO().GetString(KeyHash, "");
			Snapshot value = Snapshots.GetValue(chest, (Container _) => new Snapshot());
			if (value.Text == text)
			{
				return value;
			}
			value.Text = text;
			Revision++;
			value.Slots.Clear();
			string[] array = text.Split(';');
			for (int num = 0; num < array.Length; num++)
			{
				string[] array2 = array[num].Split(',');
				if (array2.Length == 2 && int.TryParse(array2[0], out var result) && int.TryParse(array2[1], out var result2) && result >= 0 && result2 >= 0)
				{
					value.Slots.Add(new Vector2i(result, result2));
				}
			}
			return value;
		}

		internal static bool IsLocked(Container chest, Vector2i position)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (Enabled && ChestRules.IsPlayerChest(chest))
			{
				return Read(chest).Slots.Contains(position);
			}
			return false;
		}

		internal static bool IsLocked(Container chest, ItemData item)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			if (item != null)
			{
				return IsLocked(chest, item.m_gridPos);
			}
			return false;
		}

		internal static bool EffectiveLocked(Container chest, Vector2i position)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			if (Enabled)
			{
				return PendingChanges.FirstOrDefault((Pending p) => (Object)(object)p.Chest == (Object)(object)chest && p.Position == position)?.Locked ?? IsLocked(chest, position);
			}
			return false;
		}

		internal static bool CanSet(Container chest)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			if (Enabled && ChestRules.IsPlayerChest(chest) && (Object)(object)Game.instance != (Object)null && chest.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID()))
			{
				if (chest.m_checkGuardStone && !StorageConfig.IgnoreWardOnChests.Value)
				{
					return PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, false);
				}
				return true;
			}
			return false;
		}

		private static bool InBounds(Container chest, Vector2i position)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (chest.GetInventory() != null && position.x >= 0 && position.y >= 0 && position.x < chest.GetInventory().GetWidth())
			{
				return position.y < chest.GetInventory().GetHeight();
			}
			return false;
		}

		internal static void Toggle(Container chest, Vector2i position)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			if (CanSet(chest) && InBounds(chest, position))
			{
				Pending pending = new Pending
				{
					Chest = chest,
					Position = position,
					Locked = !EffectiveLocked(chest, position),
					PlayerId = Game.instance.GetPlayerProfile().GetPlayerID(),
					SentAt = Time.unscaledTime
				};
				PendingChanges.RemoveAll((Pending p) => (Object)(object)p.Chest == (Object)(object)chest && p.Position == position);
				PendingChanges.Add(pending);
				Send(pending);
			}
		}

		private static void Apply(Container chest, Vector2i position, bool locked, long playerId)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			if (!Enabled || !ChestRules.IsPlayerChest(chest) || !chest.m_nview.IsOwner() || !InBounds(chest, position) || !chest.CheckAccess(playerId))
			{
				return;
			}
			HashSet<Vector2i> hashSet = new HashSet<Vector2i>(Read(chest).Slots);
			if (locked ? hashSet.Add(position) : hashSet.Remove(position))
			{
				chest.m_nview.GetZDO().Set("vqol_locks", string.Join(";", from p in hashSet
					orderby p.y, p.x
					select $"{p.x},{p.y}"));
			}
		}

		private static void Send(Pending pending)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			ZNetView nview = pending.Chest.m_nview;
			if (!nview.HasOwner())
			{
				nview.ClaimOwnership();
			}
			if (nview.IsOwner())
			{
				Apply(pending.Chest, pending.Position, pending.Locked, pending.PlayerId);
				return;
			}
			nview.InvokeRPC("VikingQoL_SetChestLock", new object[4]
			{
				pending.Position.x,
				pending.Position.y,
				pending.Locked,
				pending.PlayerId
			});
		}

		internal static void Tick()
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			if (PendingChanges.Count == 0)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			foreach (Pending item in PendingChanges.ToList())
			{
				if (unscaledTime - item.SentAt < 1.5f)
				{
					continue;
				}
				if (!Enabled || !ChestRules.IsPlayerChest(item.Chest) || !InBounds(item.Chest, item.Position) || IsLocked(item.Chest, item.Position) == item.Locked)
				{
					PendingChanges.Remove(item);
				}
				else if (item.Retries >= 2)
				{
					PendingChanges.Remove(item);
					Player localPlayer = Player.m_localPlayer;
					if (localPlayer != null)
					{
						((Character)localPlayer).Message((MessageType)2, "The chest didn't respond, try again.", 0, (Sprite)null, false);
					}
				}
				else
				{
					item.Retries++;
					item.SentAt = unscaledTime;
					Log.Debug($"Chest slot lock not confirmed, asking again (try {item.Retries}).");
					Send(item);
				}
			}
		}
	}
	internal static class ChestPeekData
	{
		internal sealed class Entry
		{
			public string Name;

			public int Total;

			public Sprite Icon;
		}

		internal sealed class Result
		{
			public uint Revision;

			public int Items;

			public int Limit;

			public readonly List<Entry> Shown = new List<Entry>();

			public int More;

			public string Text;
		}

		private const string TextColor = "#C0C0C0";

		private const int TextPerLine = 3;

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

		internal static Result Get(Container chest)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)chest == (Object)null || !StorageConfig.ChestPeekEnabled.Value || !StorageConfig.ShowChestPeek.Value || !ChestRules.IsPlayerChest(chest))
			{
				return null;
			}
			Inventory inventory = chest.GetInventory();
			if (inventory == null || inventory.NrOfItems() == 0)
			{
				return null;
			}
			if (!chest.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID()) || (chest.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, false)))
			{
				return null;
			}
			uint dataRevision = chest.m_nview.GetZDO().DataRevision;
			int value = StorageConfig.ChestPeekItems.Value;
			int num = inventory.NrOfItems();
			if (Cache.TryGetValue(chest, out var value2) && value2.Revision == dataRevision && value2.Items == num && value2.Limit == value)
			{
				return value2;
			}
			if (Cache.Count > 64)
			{
				foreach (Container item in Cache.Keys.Where((Container c) => (Object)(object)c == (Object)null).ToList())
				{
					Cache.Remove(item);
				}
			}
			Result result = Build(inventory, dataRevision, num, value);
			Cache[chest] = result;
			return result;
		}

		private static Result Build(Inventory inventory, uint revision, int count, int limit)
		{
			var list = (from i in inventory.GetAllItems()
				group i by i.m_shared.m_name into g
				select new
				{
					Key = g.Key,
					Total = g.Sum((ItemData i) => i.m_stack),
					First = g.First()
				} into g
				orderby g.Total descending, g.Key
				select g).ToList();
			Result result = new Result
			{
				Revision = revision,
				Items = count,
				Limit = limit,
				More = Math.Max(0, list.Count - limit)
			};
			foreach (var item in list.Take(limit))
			{
				result.Shown.Add(new Entry
				{
					Name = Localization.instance.Localize(item.Key),
					Total = item.Total,
					Icon = IconOf(item.First)
				});
			}
			return result;
		}

		private static Sprite IconOf(ItemData item)
		{
			Sprite[] icons = item.m_shared.m_icons;
			if (icons == null || item.m_variant < 0 || item.m_variant >= icons.Length)
			{
				if (icons == null || icons.Length == 0)
				{
					return null;
				}
				return icons[0];
			}
			return icons[item.m_variant];
		}

		internal static string TextOf(Result result)
		{
			if (result.Text != null)
			{
				return result.Text;
			}
			List<string> list = result.Shown.Select((Entry e) => $"{e.Name} {e.Total}").ToList();
			if (result.More > 0)
			{
				list.Add($"+{result.More} more");
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int num = 0; num < list.Count; num += 3)
			{
				if (num > 0)
				{
					stringBuilder.Append('\n');
				}
				stringBuilder.Append("<color=#C0C0C0>").Append(string.Join(" · ", list.Skip(num).Take(3))).Append("</color>");
			}
			return result.Text = stringBuilder.ToString();
		}
	}
	[HarmonyPatch]
	internal static class ChestPeekIcons
	{
		private sealed class CellUI
		{
			public GameObject Root;

			public RectTransform Rect;

			public Image Icon;

			public TextMeshProUGUI Count;
		}

		private const float CellSize = 34f;

		private const float Spacing = 4f;

		private const float Gap = 6f;

		private const int PerLine = 6;

		private static readonly List<CellUI> Cells = new List<CellUI>();

		private static GameObject _panel;

		private static RectTransform _panelRect;

		private static bool _failed;

		private static bool _ran;

		private static ChestPeekData.Result _shown;

		private static float _shownPivot = -1f;

		private static string _measuredText;

		private static GameObject _lastHover;

		private static Container _lastChest;

		internal static bool Working
		{
			get
			{
				if (_ran)
				{
					return !_failed;
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(Hud), "UpdateCrosshair")]
		[HarmonyPostfix]
		private static void UpdateCrosshair(Hud __instance, Player player)
		{
			if (_failed)
			{
				return;
			}
			_ran = true;
			try
			{
				ChestPeekData.Result result = Wanted(__instance, player);
				if (result == null)
				{
					Hide();
					return;
				}
				if ((Object)(object)_panel == (Object)null)
				{
					Setup(__instance);
				}
				Show(__instance, result);
			}
			catch (Exception arg)
			{
				_failed = true;
				Hide();
				Log.Warn($"Chest peek icons unavailable, using the text list instead: {arg}");
			}
		}

		internal static void Tick()
		{
			if ((Object)(object)_panel != (Object)null && _panel.activeSelf && (Object)(object)Player.m_localPlayer == (Object)null)
			{
				Hide();
			}
		}

		private static ChestPeekData.Result Wanted(Hud hud, Player player)
		{
			if (StorageConfig.ChestPeekStyle.Value != ChestPeekStyle.Icons || ((TMP_Text)hud.m_hoverName).text.Length == 0)
			{
				return null;
			}
			GameObject hoverObject = ((Humanoid)player).GetHoverObject();
			if ((Object)(object)hoverObject != (Object)(object)_lastHover)
			{
				_lastHover = hoverObject;
				_lastChest = (Object.op_Implicit((Object)(object)hoverObject) ? hoverObject.GetComponentInParent<Container>() : null);
			}
			if (!((Object)(object)_lastChest != (Object)null))
			{
				return null;
			}
			return ChestPeekData.Get(_lastChest);
		}

		private static void Setup(Hud hud)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI hoverName = hud.m_hoverName;
			Cells.Clear();
			_shown = null;
			_measuredText = null;
			_lastHover = null;
			_lastChest = null;
			_panel = new GameObject("VikingQoL_ChestPeek", new Type[1] { typeof(RectTransform) });
			_panel.transform.SetParent(((TMP_Text)hoverName).transform.parent, false);
			_panelRect = (RectTransform)_panel.transform;
			RectTransform panelRect = _panelRect;
			RectTransform panelRect2 = _panelRect;
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0.5f, 0.5f);
			panelRect2.anchorMax = val;
			panelRect.anchorMin = val;
			_panel.AddComponent<LayoutElement>().ignoreLayout = true;
			_panel.SetActive(false);
			string name = ((Object)((TMP_Text)hoverName).transform.parent).name;
			object arg = ((TMP_Text)hoverName).horizontalAlignment;
			Rect rect = ((TMP_Text)hoverName).rectTransform.rect;
			Log.Debug("Chest peek panel created next to the hover text (parent '" + name + "', " + $"alignment {arg}, rect {((Rect)(ref rect)).size}).");
		}

		private static CellUI NewCell(TextMeshProUGUI hoverText)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: 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_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Cell", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			});
			val.transform.SetParent(_panel.transform, false);
			RectTransform val2 = (RectTransform)val.transform;
			Vector2 val3 = default(Vector2);
			((Vector2)(ref val3))..ctor(0f, 1f);
			val2.pivot = val3;
			Vector2 anchorMin = (val2.anchorMax = val3);
			val2.anchorMin = anchorMin;
			val2.sizeDelta = new Vector2(34f, 34f);
			Image component = val.GetComponent<Image>();
			((Graphic)component).color = new Color(0f, 0f, 0f, 0.45f);
			((Graphic)component).raycastTarget = false;
			GameObject val5 = new GameObject("Icon", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			});
			val5.transform.SetParent(val.transform, false);
			RectTransform val6 = (RectTransform)val5.transform;
			val6.anchorMin = Vector2.zero;
			val6.anchorMax = Vector2.one;
			val6.offsetMin = new Vector2(2f, 2f);
			val6.offsetMax = new Vector2(-2f, -2f);
			Image component2 = val5.GetComponent<Image>();
			component2.preserveAspect = true;
			((Graphic)component2).raycastTarget = false;
			GameObject val7 = new GameObject("Count", new Type[2]
			{
				typeof(RectTransform),
				typeof(TextMeshProUGUI)
			});
			val7.transform.SetParent(val.transform, false);
			RectTransform val8 = (RectTransform)val7.transform;
			val8.anchorMin = Vector2.zero;
			val8.anchorMax = Vector2.one;
			val8.offsetMin = new Vector2(1f, 0f);
			val8.offsetMax = new Vector2(-2f, 0f);
			TextMeshProUGUI component3 = val7.GetComponent<TextMeshProUGUI>();
			((TMP_Text)component3).font = ((TMP_Text)hoverText).font;
			((TMP_Text)component3).fontSharedMaterial = ((TMP_Text)hoverText).fontSharedMaterial;
			((TMP_Text)component3).fontSize = 13f;
			((TMP_Text)component3).alignment = (TextAlignmentOptions)1028;
			((Graphic)component3).color = Color.white;
			((TMP_Text)component3).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)component3).overflowMode = (TextOverflowModes)0;
			((Graphic)component3).raycastTarget = false;
			return new CellUI
			{
				Root = val,
				Rect = val2,
				Icon = component2,
				Count = component3
			};
		}

		private static void Show(Hud hud, ChestPeekData.Result data)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI hoverName = hud.m_hoverName;
			float num = PivotX(hoverName);
			string text = ((TMP_Text)hoverName).text;
			if (!string.Equals(text, _measuredText, StringComparison.Ordinal))
			{
				((TMP_Text)hoverName).ForceMeshUpdate(false, false);
				_measuredText = text;
			}
			if (data != _shown || !Mathf.Approximately(num, _shownPivot))
			{
				Rebuild(hoverName, data, num);
			}
			Bounds textBounds = ((TMP_Text)hoverName).textBounds;
			float num2 = ((num < 0.25f) ? ((Bounds)(ref textBounds)).min.x : ((num > 0.75f) ? ((Bounds)(ref textBounds)).max.x : ((Bounds)(ref textBounds)).center.x));
			Vector3 position = ((TMP_Text)hoverName).transform.TransformPoint(new Vector3(num2, ((Bounds)(ref textBounds)).min.y - 6f, 0f));
			_panelRect.pivot = new Vector2(num, 1f);
			((Transform)_panelRect).position = position;
			if (!_panel.activeSelf)
			{
				_panel.SetActive(true);
			}
		}

		private static float PivotX(TextMeshProUGUI hoverText)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Invalid comparison between Unknown and I4
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			HorizontalAlignmentOptions horizontalAlignment = ((TMP_Text)hoverText).horizontalAlignment;
			if ((int)horizontalAlignment != 2)
			{
				if ((int)horizontalAlignment == 4)
				{
					return 1f;
				}
				if ((int)horizontalAlignment != 32)
				{
					return 0f;
				}
			}
			return 0.5f;
		}

		private static void Rebuild(TextMeshProUGUI hoverText, ChestPeekData.Result data, float pivotX)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			_shown = data;
			_shownPivot = pivotX;
			int num = data.Shown.Count + ((data.More > 0) ? 1 : 0);
			int num2 = Mathf.Min(num, 6);
			int num3 = (num + 6 - 1) / 6;
			_panelRect.sizeDelta = new Vector2((float)num2 * 34f + (float)(num2 - 1) * 4f, (float)num3 * 34f + (float)(num3 - 1) * 4f);
			while (Cells.Count < num)
			{
				Cells.Add(NewCell(hoverText));
			}
			for (int i = 0; i < Cells.Count; i++)
			{
				CellUI cellUI = Cells[i];
				if (i >= num)
				{
					cellUI.Root.SetActive(false);
					continue;
				}
				int num4 = i / 6;
				int num5 = Mathf.Min(6, num - num4 * 6);
				float num6 = (float)(num2 - num5) * 38f * pivotX;
				cellUI.Rect.anchoredPosition = new Vector2(num6 + (float)(i % 6) * 38f, (float)(-num4) * 38f);
				cellUI.Root.SetActive(true);
				if (i < data.Shown.Count)
				{
					ChestPeekData.Entry entry = data.Shown[i];
					cellUI.Icon.sprite = entry.Icon;
					((Behaviour)cellUI.Icon).enabled = (Object)(object)entry.Icon != (Object)null;
					((TMP_Text)cellUI.Count).alignment = (TextAlignmentOptions)1028;
					((TMP_Text)cellUI.Count).fontSize = 13f;
					((TMP_Text)cellUI.Count).text = Numbers.Short(entry.Total);
				}
				else
				{
					((Behaviour)cellUI.Icon).enabled = false;
					((TMP_Text)cellUI.Count).alignment = (TextAlignmentOptions)514;
					((TMP_Text)cellUI.Count).fontSize = 14f;
					((TMP_Text)cellUI.Count).text = "+" + data.More;
				}
			}
		}

		private static void Hide()
		{
			if ((Object)(object)_panel != (Object)null && _panel.activeSelf)
			{
				_panel.SetActive(false);
			}
			_shown = null;
		}
	}
	[HarmonyPatch]
	internal static class ChestRules
	{
		internal static bool IsPlayerChest(Container container)
		{
			if ((Object)(object)container == (Object)null || (Object)(object)container.m_nview == (Object)null || !container.m_nview.IsValid())
			{
				return false;
			}
			if ((Object)(object)container.m_wagon != (Object)null || (Object)(object)container.m_rootObjectOverride != (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Component)container).GetComponentInParent<Ship>() != (Object)null || (Object)(object)((Component)container).GetComponent<TombStone>() != (Object)null)
			{
				return false;
			}
			if ((Object)(object)container.m_piece != (Object)null)
			{
				return container.m_piece.IsPlacedByPlayer();
			}
			return false;
		}

		internal static bool IsShared(Container container)
		{
			if (StorageConfig.SharedChestsEnabled.Value)
			{
				return IsPlayerChest(container);
			}
			return false;
		}

		[HarmonyPatch(typeof(Container), "CheckAccess")]
		[HarmonyPostfix]
		private static void CheckAccess(Container __instance, ref bool __result)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			if (!__result && (int)__instance.m_privacy == 0 && StorageConfig.IgnorePersonalChestLock.Value && IsPlayerChest(__instance))
			{
				__result = true;
			}
		}

		[HarmonyPatch(typeof(Container), "Interact")]
		[HarmonyPrefix]
		private static void InteractPrefix(Container __instance, out bool __state)
		{
			__state = SuspendWard(__instance);
		}

		[HarmonyPatch(typeof(Container), "Interact")]
		[HarmonyFinalizer]
		private static void InteractFinalizer(Container __instance, bool __state)
		{
			RestoreWard(__instance, __state);
		}

		[HarmonyPatch(typeof(Container), "GetHoverText")]
		[HarmonyPrefix]
		private static void HoverPrefix(Container __instance, out bool __state)
		{
			__state = SuspendWard(__instance);
		}

		[HarmonyPatch(typeof(Container), "GetHoverText")]
		[HarmonyFinalizer]
		private static void HoverFinalizer(Container __instance, bool __state)
		{
			RestoreWard(__instance, __state);
		}

		private static bool SuspendWard(Container container)
		{
			if (!container.m_checkGuardStone || !StorageConfig.IgnoreWardOnChests.Value || !IsPlayerChest(container))
			{
				return false;
			}
			container.m_checkGuardStone = false;
			return true;
		}

		private static void RestoreWard(Container container, bool suspended)
		{
			if (suspended)
			{
				container.m_checkGuardStone = true;
			}
		}
	}
	[HarmonyPatch]
	internal static class ChestViewers
	{
		private sealed class Viewer
		{
			public string Name;

			public float LastSeen;
		}

		private const string RpcViewer = "VikingQoL_ChestViewer";

		private const string ZdoViewersKey = "vqol_viewers";

		private const string ZdoViewersTimeKey = "vqol_viewers_t";

		private const float PingInterval = 2f;

		private const float ViewerExpiry = 6f;

		private const float HeartbeatInterval = 5f;

		private const float NamesExpiry = 15f;

		private const float OpenRetryAfter = 1.2f;

		private const int OpenMaxRetries = 2;

		private static readonly Dictionary<Container, Dictionary<long, Viewer>> Remote = new Dictionary<Container, Dictionary<long, Viewer>>();

		private static float _nextPrune;

		private static float _nextHeartbeat;

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

		private static Container _viewing;

		private static float _lastPing;

		private static Container _pendingOpen;

		private static float _pendingOpenAt;

		private static int _pendingOpenTries;

		[HarmonyPatch(typeof(Container), "Awake")]
		[HarmonyPostfix]
		private static void ContainerAwake(Container __instance)
		{
			ZNetView nview = __instance.m_nview;
			if (!((Object)(object)nview == (Object)null) && nview.GetZDO() != null)
			{
				Container container = __instance;
				nview.Register<bool, string>("VikingQoL_ChestViewer", (Action<long, bool, string>)delegate(long sender, bool open, string name)
				{
					OnViewer(container, sender, open, name);
				});
			}
		}

		[HarmonyPatch(typeof(Container), "RPC_RequestOpen")]
		[HarmonyPrefix]
		private static bool RequestOpen(Container __instance, long uid, long playerID)
		{
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			if (!ChestRules.IsShared(__instance) || !__instance.m_nview.IsOwner())
			{
				return true;
			}
			if ((!__instance.IsInUse() || uid == ZNet.GetUID()) && !HasOtherViewers(__instance, uid))
			{
				return true;
			}
			if (!__instance.CheckAccess(playerID))
			{
				__instance.m_nview.InvokeRPC(uid, "RPC_OpenResponse", new object[1] { false });
				return false;
			}
			Log.Debug($"Shared open: {uid} joins {((Object)__instance).name} (owner keeps it).");
			ZDOMan.instance.ForceSendZDO(uid, __instance.m_nview.GetZDO().m_uid);
			__instance.m_nview.InvokeRPC(uid, "RPC_OpenResponse", new object[1] { true });
			return false;
		}

		[HarmonyPatch(typeof(Container), "Interact")]
		[HarmonyPostfix]
		private static void Interact(Container __instance, bool hold, bool __result)
		{
			if (!hold && __result && ChestRules.IsPlayerChest(__instance) && __instance.CheckAccess(Game.instance.GetPlayerProfile().GetPlayerID()))
			{
				_pendingOpen = __instance;
				_pendingOpenAt = Time.unscaledTime;
				_pendingOpenTries = 0;
			}
		}

		[HarmonyPatch(typeof(Container), "RPC_OpenResponse")]
		[HarmonyPrefix]
		private static void OpenResponse(Container __instance)
		{
			if ((Object)(object)_pendingOpen == (Object)(object)__instance)
			{
				_pendingOpen = null;
			}
		}

		private static void OnViewer(Container chest, long sender, bool open, string name)
		{
			if (chest.IsOwner())
			{
				if (!Remote.TryGetValue(chest, out var value))
				{
					value = (Remote[chest] = new Dictionary<long, Viewer>());
				}
				if (open)
				{
					value[sender] = new Viewer
					{
						Name = name,
						LastSeen = Time.unscaledTime
					};
				}
				else
				{
					value.Remove(sender);
				}
				Publish(chest);
			}
		}

		private static bool HasOtherViewers(Container chest, long uid)
		{
			Prune(chest);
			if (Remote.TryGetValue(chest, out var value))
			{
				return value.Keys.Any((long k) => k != uid);
			}
			return false;
		}

		private static bool HasRemoteViewers(Container chest)
		{
			if (Remote.TryGetValue(chest, out var value))
			{
				return value.Count > 0;
			}
			return false;
		}

		private static void Prune(Container chest)
		{
			if (!Remote.TryGetValue(chest, out var value))
			{
				return;
			}
			float now = Time.unscaledTime;
			foreach (long item in (from v in value
				where now - v.Value.LastSeen > 6f
				select v.Key).ToList())
			{
				value.Remove(item);
			}
		}

		private static void Publish(Container chest)
		{
			if ((Object)(object)chest == (Object)null || !chest.m_nview.IsValid() || !chest.IsOwner())
			{
				return;
			}
			List<string> list = new List<string>();
			if (chest.IsInUse() && Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				list.Add(Player.m_localPlayer.GetPlayerName());
			}
			if (Remote.TryGetValue(chest, out var value))
			{
				list.AddRange(value.Values.Select((Viewer v) => v.Name));
			}
			ZDO zDO = chest.m_nview.GetZDO();
			string text = string.Join(", ", list.Distinct());
			if (zDO.GetString("vqol_viewers", "") != text)
			{
				zDO.Set("vqol_viewers", text);
			}
			if (text.Length > 0)
			{
				zDO.Set("vqol_viewers_t", (float)ZNet.instance.GetTimeSeconds());
			}
			if (text.Length > 0)
			{
				Occupied.Add(chest);
			}
			else
			{
				Occupied.Remove(chest);
			}
			chest.UpdateUseVisual();
		}

		[HarmonyPatch(typeof(Container), "SetInUse")]
		[HarmonyPrefix]
		private static void SetInUsePrefix(Container __instance, out bool __state)
		{
			__state = __instance.m_inUse;
		}

		[HarmonyPatch(typeof(Container), "SetInUse")]
		[HarmonyPostfix]
		private static void SetInUse(Container __instance, bool __state)
		{
			if (__instance.m_inUse != __state && ChestRules.IsShared(__instance))
			{
				Publish(__instance);
			}
		}

		[HarmonyPatch(typeof(Container), "UpdateUseVisual")]
		[HarmonyPostfix]
		private static void UpdateUseVisual(Container __instance)
		{
			if (__instance.m_nview.IsValid() && __instance.IsOwner() && HasRemoteViewers(__instance))
			{
				__instance.m_nview.GetZDO().Set(ZDOVars.s_inUse, 1, false);
				if (Object.op_Implicit((Object)(object)__instance.m_open))
				{
					__instance.m_open.SetActive(true);
				}
				if (Object.op_Implicit((Object)(object)__instance.m_closed))
				{
					__instance.m_closed.SetActive(false);
				}
			}
		}

		internal static string OpenByLine(Container chest)
		{
			if (!StorageConfig.ShowViewersInHoverText.Value || !ChestRules.IsShared(chest))
			{
				return null;
			}
			ZDO zDO = chest.m_nview.GetZDO();
			string text = zDO.GetString("vqol_viewers", "");
			float num = (float)ZNet.instance.GetTimeSeconds() - zDO.GetFloat("vqol_viewers_t", 0f);
			if (string.IsNullOrEmpty(text) || !(num < 15f))
			{
				return null;
			}
			return "<color=#B0B0B0>Open by: " + text + "</color>";
		}

		internal static void Tick()
		{
			float unscaledTime = Time.unscaledTime;
			TickViewing(unscaledTime);
			TickPendingOpen(unscaledTime);
			if (unscaledTime >= _nextPrune)
			{
				_nextPrune = unscaledTime + 1f;
				foreach (Container item in Remote.Keys.ToList())
				{
					if ((Object)(object)item == (Object)null || !item.m_nview.IsValid() || !item.IsOwner())
					{
						Remote.Remove(item);
						continue;
					}
					int count = Remote[item].Count;
					Prune(item);
					if (Remote[item].Count != count)
					{
						Publish(item);
					}
				}
			}
			if (!(unscaledTime >= _nextHeartbeat))
			{
				return;
			}
			_nextHeartbeat = unscaledTime + 5f;
			foreach (Container item2 in Occupied.ToList())
			{
				if ((Object)(object)item2 == (Object)null || !item2.m_nview.IsValid() || !item2.IsOwner())
				{
					Occupied.Remove(item2);
				}
				else
				{
					Publish(item2);
				}
			}
		}

		private static void TickViewing(float now)
		{
			Container val = (Object.op_Implicit((Object)(object)InventoryGui.instance) ? InventoryGui.instance.m_currentContainer : null);
			bool num = (Object)(object)val != (Object)null && val.m_nview.IsValid() && !val.IsOwner() && ChestRules.IsShared(val);
			if ((Object)(object)_viewing != (Object)null && (Object)(object)_viewing != (Object)(object)val)
			{
				SendViewer(_viewing, open: false);
				_viewing = null;
			}
			if (num)
			{
				val.m_inUse = false;
				val.CheckForChanges();
				if ((Object)(object)_viewing != (Object)(object)val || now - _lastPing >= 2f)
				{
					SendViewer(val, open: true);
					_viewing = val;
					_lastPing = now;
				}
			}
		}

		private static void SendViewer(Container chest, bool open)
		{
			if (!((Object)(object)chest == (Object)null) && chest.m_nview.IsValid())
			{
				string text = (Object.op_Implicit((Object)(object)Player.m_localPlayer) ? Player.m_localPlayer.GetPlayerName() : "?");
				chest.m_nview.InvokeRPC("VikingQoL_ChestViewer", new object[2] { open, text });
			}
		}

		private static void TickPendingOpen(float now)
		{
			if ((Object)(object)_pendingOpen == (Object)null)
			{
				return;
			}
			if (!_pendingOpen.m_nview.IsValid() || (Object)(object)InventoryGui.instance?.m_currentContainer == (Object)(object)_pendingOpen)
			{
				_pendingOpen = null;
			}
			else
			{
				if (now - _pendingOpenAt < 1.2f)
				{
					return;
				}
				if (_pendingOpenTries >= 2)
				{
					Player localPlayer = Player.m_localPlayer;
					if (localPlayer != null)
					{
						((Character)localPlayer).Message((MessageType)2, "The chest didn't respond, try again.", 0, (Sprite)null, false);
					}
					_pendingOpen = null;
				}
				else
				{
					_pendingOpenTries++;
					_pendingOpenAt = now;
					Log.Debug($"Chest open not answered, asking again (owner {_pendingOpen.m_nview.GetZDO().GetOwner()}).");
					_pendingOpen.m_nview.InvokeRPC("RPC_RequestOpen", new object[1] { Game.instance.GetPlayerProfile().GetPlayerID() });
				}
			}
		}
	}
	internal enum OpStatus : byte
	{
		Ok,
		NotOwner,
		Invalid,
		Gone,
		Timeout
	}
	internal struct TakeItem
	{
		public Vector2i Pos;

		public string Name;

		public int Quality;

		public int Amount;

		public static TakeItem From(ItemData item, int amount)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			return new TakeItem
			{
				Pos = item.m_gridPos,
				Name = item.m_shared.m_name,
				Quality = item.m_quality,
				Amount = amount
			};
		}
	}
	[HarmonyPatch]
	internal static class ContainerService
	{
		private enum OpType : byte
		{
			Take = 1,
			Put,
			Move,
			StackInto,
			Sort,
			TakeUnlocked
		}

		private sealed class Pending
		{
			public int Id;

			public Container Container;

			public byte[] Request;

			public Inventory SentItems;

			public Action<OpStatus, Inventory> Done;

			public float SentAt;

			public float RetryAt = -1f;

			public int Retries;
		}

		private const string RpcRequest = "VikingQoL_ChestOp";

		private const string RpcResult = "VikingQoL_ChestOpResult";

		private const float TimeoutSeconds = 3f;

		private const int MaxNotOwnerRetries = 3;

		private const float RetryDelaySeconds = 0.3f;

		private const int TempWidth = 16;

		private const int TempHeight = 64;

		private static readonly Dictionary<int, Pending> PendingOps = new Dictionary<int, Pending>();

		private static int _nextId = 1;

		internal static bool Busy => PendingOps.Count > 0;

		[HarmonyPatch(typeof(Container), "Awake")]
		[HarmonyPostfix]
		private static void ContainerAwake(Container __instance)
		{
			ZNetView nview = __instance.m_nview;
			if (!((Object)(object)nview == (Object)null) && nview.GetZDO() != null)
			{
				Container container = __instance;
				nview.Register<ZPackage>("VikingQoL_ChestOp", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
				{
					OnRequest(container, sender, pkg);
				});
				nview.Register<ZPackage>("VikingQoL_ChestOpResult", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg)
				{
					OnResult(container, pkg);
				});
			}
		}

		internal static void Take(Container chest, IList<TakeItem> items, Action<OpStatus, Inventory> done, bool respectLocks = true)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = Begin((!respectLocks) ? OpType.Take : OpType.TakeUnlocked);
			val.Write(items.Count);
			foreach (TakeItem item in items)
			{
				val.Write(item.Pos.x);
				val.Write(item.Pos.y);
				val.Write(item.Name);
				val.Write(item.Quality);
				val.Write(item.Amount);
			}
			Send(chest, val, null, done);
		}

		internal static void Put(Container chest, Inventory items, Vector2i? target, bool allowSwap, Action<OpStatus, Inventory> done)
		{
			//IL_0023: 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)
			ZPackage val = Begin(OpType.Put);
			val.Write(target.HasValue);
			val.Write(target?.x ?? (-1));
			val.Write(target?.y ?? (-1));
			val.Write(allowSwap);
			val.Write(Pack(items));
			Send(chest, val, items, done);
		}

		internal static void Move(Container chest, ItemData item, int amount, Vector2i to, Action<OpStatus, Inventory> done)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = Begin(OpType.Move);
			val.Write(item.m_gridPos.x);
			val.Write(item.m_gridPos.y);
			val.Write(to.x);
			val.Write(to.y);
			val.Write(amount);
			val.Write(item.m_shared.m_name);
			val.Write(item.m_quality);
			Send(chest, val, null, done);
		}

		internal static void StackInto(Container chest, Inventory items, Action<OpStatus, Inventory> done)
		{
			ZPackage val = Begin(OpType.StackInto);
			val.Write(Pack(items));
			Send(chest, val, items, done);
		}

		internal static void Sort(Container chest, SortMode mode, Action<OpStatus, Inventory> done)
		{
			ZPackage val = Begin(OpType.Sort);
			val.Write((int)mode);
			Send(chest, val, null, done);
		}

		private static ZPackage Begin(OpType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write((byte)type);
			return val;
		}

		private static void Send(Container chest, ZPackage body, Inventory sentItems, Action<OpStatus, Inventory> done)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			byte[] array = body.GetArray();
			if (chest.IsOwner())
			{
				OpStatus status;
				Inventory items = Execute(chest, new ZPackage(array), out status);
				Finish(done, status, items);
				return;
			}
			Pending pending = new Pending
			{
				Id = _nextId++,
				Container = chest,
				Request = array,
				SentItems = sentItems,
				Done = done
			};
			PendingOps[pending.Id] = pending;
			Transmit(pending);
		}

		private static void Transmit(Pending pending)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			pending.SentAt = Time.unscaledTime;
			pending.RetryAt = -1f;
			ZPackage val = new ZPackage();
			val.Write(pending.Id);
			val.Write(pending.Request);
			pending.Container.m_nview.InvokeRPC("VikingQoL_ChestOp", new object[1] { val });
			Log.Debug($"Chest op {pending.Id} sent to owner {pending.Container.m_nview.GetZDO().GetOwner()}.");
		}

		private static void OnResult(Container chest, ZPackage pkg)
		{
			int key = pkg.ReadInt();
			OpStatus opStatus = (OpStatus)pkg.ReadByte();
			byte[] data = pkg.ReadByteArray();
			byte[] array = pkg.ReadByteArray();
			if (!PendingOps.TryGetValue(key, out var value))
			{
				return;
			}
			if (opStatus == OpStatus.NotOwner && value.Retries < 3)
			{
				value.Retries++;
				value.RetryAt = Time.unscaledTime + 0.3f;
				return;
			}
			PendingOps.Remove(key);
			if (array.Length != 0 && !chest.IsOwner())
			{
				ApplySnapshot(chest, array);
			}
			Inventory items = ((opStatus == OpStatus.NotOwner) ? value.SentItems : Unpack(data));
			Finish(value.Done, opStat