Decompiled source of SmartContainersIgnore v1.5.0

SmartContainersIgnore.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: AssemblyFileVersion("1.5.0.0")]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("1.5.0.0")]
namespace SmartContainersIgnore;

internal static class ChestPolicy
{
	internal const string Key = "niclas.smartcontainers.ignore.v1";

	internal static bool IsExcluded(ZNetView view)
	{
		if (!Object.op_Implicit((Object)(object)view) || !view.IsValid())
		{
			return true;
		}
		ZDO zDO = view.GetZDO();
		if (zDO != null)
		{
			return zDO.GetBool("niclas.smartcontainers.ignore.v1", false);
		}
		return true;
	}

	internal static bool CanToggle(ZNetView view, bool hasAccess)
	{
		if (!hasAccess || !Object.op_Implicit((Object)(object)view) || !view.IsValid() || !view.IsOwner())
		{
			return false;
		}
		ZDO zDO = view.GetZDO();
		if (zDO != null)
		{
			return zDO.Persistent;
		}
		return false;
	}

	internal static bool TryToggle(ZNetView view, bool hasAccess, out bool excluded)
	{
		excluded = false;
		if (!CanToggle(view, hasAccess))
		{
			return false;
		}
		ZDO zDO = view.GetZDO();
		excluded = !zDO.GetBool("niclas.smartcontainers.ignore.v1", false);
		zDO.Set("niclas.smartcontainers.ignore.v1", excluded);
		return true;
	}
}
internal static class ContentsCache
{
	private sealed class Entry
	{
		internal bool Dirty = true;

		internal readonly SingleItemHint Hint = new SingleItemHint();
	}

	private static readonly ConditionalWeakTable<Inventory, Entry> entries = new ConditionalWeakTable<Inventory, Entry>();

	internal static void Changed(Inventory inventory)
	{
		if (inventory != null)
		{
			entries.GetOrCreateValue(inventory).Dirty = true;
		}
	}

	internal static void Fresh(Inventory inventory)
	{
		if (inventory != null)
		{
			entries.GetOrCreateValue(inventory).Dirty = false;
		}
	}

	internal static bool IsDirty(Inventory inventory)
	{
		if (inventory != null && entries.TryGetValue(inventory, out var value))
		{
			return value.Dirty;
		}
		return false;
	}

	internal static bool Rejects(Inventory inventory, ZDO data, ItemData item)
	{
		if (inventory != null && entries.TryGetValue(inventory, out var value))
		{
			return value.Hint.Rejects(data, value.Dirty, item.m_shared.m_name, Game.m_worldLevel);
		}
		return false;
	}

	internal static string Describe(Inventory inventory)
	{
		string text = null;
		int num = int.MinValue;
		List<ItemData> allItems = inventory.GetAllItems();
		foreach (ItemData item in allItems)
		{
			if (item == null || item.m_shared == null || string.IsNullOrEmpty(item.m_shared.m_name))
			{
				return "";
			}
			if (text != null && text != item.m_shared.m_name)
			{
				return "";
			}
			text = item.m_shared.m_name;
			num = Math.Max(num, item.m_worldLevel);
		}
		return SingleItemHint.Encode(text, num);
	}
}
internal static class DirectTransferScope
{
	[ThreadStatic]
	private static int depth;

	internal static bool Active => depth > 0;

	internal static void Run(Action transfer)
	{
		if (transfer == null)
		{
			throw new ArgumentNullException("transfer");
		}
		depth++;
		try
		{
			transfer();
		}
		finally
		{
			depth--;
		}
	}
}
internal sealed class GroupingConfigGuard : IDisposable
{
	private readonly ConfigEntry<bool> entry;

	private readonly Action<Exception> report;

	private bool resaveAfterReload;

	internal GroupingConfigGuard(ConfigEntry<bool> entry, Action<Exception> report)
	{
		if (entry == null)
		{
			throw new ArgumentNullException("entry");
		}
		if (report == null)
		{
			throw new ArgumentNullException("report");
		}
		this.entry = entry;
		this.report = report;
		entry.SettingChanged += OnChanged;
		((ConfigEntryBase)entry).ConfigFile.ConfigReloaded += OnReloaded;
		ForceOff(reloaded: false);
		resaveAfterReload = false;
	}

	private void OnChanged(object sender, EventArgs args)
	{
		ForceOff(reloaded: false);
	}

	private void OnReloaded(object sender, EventArgs args)
	{
		ForceOff(reloaded: true);
	}

	private void ForceOff(bool reloaded)
	{
		try
		{
			bool value = entry.Value;
			if (value)
			{
				resaveAfterReload = true;
				entry.Value = false;
			}
			if ((reloaded && resaveAfterReload) || (value && !((ConfigEntryBase)entry).ConfigFile.SaveOnConfigSet))
			{
				((ConfigEntryBase)entry).ConfigFile.Save();
			}
			if (reloaded)
			{
				resaveAfterReload = false;
			}
		}
		catch (Exception obj)
		{
			report(obj);
		}
	}

	public void Dispose()
	{
		entry.SettingChanged -= OnChanged;
		((ConfigEntryBase)entry).ConfigFile.ConfigReloaded -= OnReloaded;
	}
}
[BepInProcess("valheim.exe")]
[BepInPlugin("niclas.smartcontainers.ignore", "SmartContainers Ignore", "1.5.0")]
[BepInDependency("flueno.SmartContainers", "1.8.3")]
public sealed class Plugin : BaseUnityPlugin
{
	internal const string Id = "niclas.smartcontainers.ignore";

	internal const string SmartId = "flueno.SmartContainers";

	private static Plugin instance;

	private Harmony harmony;

	private GroupingConfigGuard groupingGuard;

	private static FieldInfo currentContainer;

	private static FieldInfo containerView;

	private static FieldInfo grouping;

	private static FieldInfo master;

	private static FieldInfo buttonPosition;

	private static FieldInfo dragObject;

	private static FieldInfo lastRevision;

	private static FieldInfo rearranging;

	private static FieldInfo trackedContainers;

	private static FieldInfo onlyStackable;

	private static FieldInfo keyModifier;

	private static FieldInfo groupingModifier;

	private static FieldInfo gamepadKey1;

	private static FieldInfo gamepadKey2;

	private static Func<KeyboardShortcut, bool> gamepadPressed;

	private static Func<ItemData, bool, bool, bool> routeItems;

	private static Func<Inventory, ItemData, bool> nativeAdd;

	private static Func<string, string, Vector2, Vector2, Transform> createButton;

	private static Func<Container, long, bool> checkAccess;

	private static Action<InventoryGui, InventoryGrid, ItemData, Vector2i, Modifier> selectItem;

	private static Button button;

	private static TMP_Text label;

	private static UITooltip tooltip;

	private static Button priorityButton;

	private static TMP_Text priorityLabel;

	private static UITooltip priorityTooltip;

	private static bool ready;

	private static bool uiFault;

	private static bool routingFaultLogged;

	private static bool cacheFault;

	private void Awake()
	{
		//IL_06eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_06f5: Expected O, but got Unknown
		instance = this;
		try
		{
			if (!Chainloader.PluginInfos.TryGetValue("flueno.SmartContainers", out var value) || !Object.op_Implicit((Object)(object)value.Instance))
			{
				throw new NotSupportedException("SmartContainers 1.8.3 or newer is required.");
			}
			Assembly assembly = ((object)value.Instance).GetType().Assembly;
			Type type = assembly.GetType("SmartContainers.Mod", throwOnError: true);
			Type type2 = assembly.GetType("SmartContainers.Inventory_Patch", throwOnError: true);
			Type type3 = assembly.GetType("SmartContainers.Gui_Patch", throwOnError: true);
			currentContainer = Field(typeof(InventoryGui), "m_currentContainer", typeof(Container));
			dragObject = Field(typeof(InventoryGui), "m_dragGo", typeof(GameObject));
			selectItem = (Action<InventoryGui, InventoryGrid, ItemData, Vector2i, Modifier>)Delegate.CreateDelegate(typeof(Action<InventoryGui, InventoryGrid, ItemData, Vector2i, Modifier>), Method(typeof(InventoryGui), "OnSelectedItem", typeof(void), typeof(InventoryGrid), typeof(ItemData), typeof(Vector2i), typeof(Modifier)));
			containerView = Field(typeof(Container), "m_nview", typeof(ZNetView));
			lastRevision = Field(typeof(Container), "m_lastRevision", typeof(uint));
			checkAccess = (Func<Container, long, bool>)Delegate.CreateDelegate(typeof(Func<Container, long, bool>), Method(typeof(Container), "CheckAccess", typeof(bool), typeof(long)));
			grouping = Field(type, "groupingEnabled", typeof(ConfigEntry<bool>));
			master = Field(type, "modEnabled", typeof(ConfigEntry<bool>));
			buttonPosition = Field(type, "createGroupBtnPos", typeof(ConfigEntry<Vector2>));
			onlyStackable = Field(type, "onlyStackableItems", typeof(ConfigEntry<bool>));
			keyModifier = Field(type, "keyModifier", typeof(ConfigEntry<KeyboardShortcut>));
			groupingModifier = Field(type, "groupingKeyModifier", typeof(ConfigEntry<KeyboardShortcut>));
			gamepadKey1 = Field(type, "gamepadKey1", typeof(ConfigEntry<string>));
			gamepadKey2 = Field(type, "gamepadKey2", typeof(ConfigEntry<KeyboardShortcut>));
			gamepadPressed = (Func<KeyboardShortcut, bool>)Delegate.CreateDelegate(typeof(Func<KeyboardShortcut, bool>), Method(assembly.GetType("SmartContainers.KeyboardShortcutMethods", throwOnError: true), "MainPressed", typeof(bool), typeof(KeyboardShortcut)));
			MethodInfo method = Method(assembly.GetType("SmartContainers.GuiFactory", throwOnError: true), "AddContainerGuiButton", typeof(Transform), typeof(string), typeof(string), typeof(Vector2), typeof(Vector2));
			createButton = (Func<string, string, Vector2, Vector2, Transform>)Delegate.CreateDelegate(typeof(Func<string, string, Vector2, Vector2, Transform>), method);
			Type type4 = assembly.GetType("SmartContainers.ContainersTracker", throwOnError: true);
			rearranging = Field(type4, "isRearrangingItem", typeof(bool));
			trackedContainers = Field(type4, "containerList", typeof(ICollection<Container>));
			MethodInfo methodInfo = Method(type4, "GetNearbyContainers", typeof(List<Container>), typeof(Vector3), typeof(bool));
			MethodInfo methodInfo2 = Method(type2, "AddItemToContainer", typeof(bool), typeof(ItemData), typeof(Container), typeof(bool));
			MethodInfo methodInfo3 = Method(type2, "TryAddToNearBy", typeof(bool), typeof(ItemData), typeof(bool), typeof(bool));
			routeItems = (Func<ItemData, bool, bool, bool>)Delegate.CreateDelegate(typeof(Func<ItemData, bool, bool, bool>), methodInfo3);
			MethodInfo methodInfo4 = Method(typeof(Inventory), "AddItem", typeof(bool), typeof(ItemData));
			nativeAdd = (Func<Inventory, ItemData, bool>)Delegate.CreateDelegate(typeof(Func<Inventory, ItemData, bool>), methodInfo4);
			MethodInfo methodInfo5 = Method(typeof(Inventory), "MoveItemToThis", typeof(void), typeof(Inventory), typeof(ItemData));
			MethodInfo methodInfo6 = Method(typeof(Inventory), "Changed", typeof(void), typeof(bool), typeof(bool));
			MethodInfo methodInfo7 = Method(typeof(Container), "Save", typeof(void));
			MethodInfo methodInfo8 = Method(typeof(Container), "Load", typeof(bool));
			MethodInfo methodInfo9 = Method(type3, "AddCreateGroupBtn", typeof(void), typeof(InventoryGui));
			MethodInfo methodInfo10 = Method(typeof(InventoryGui), "UpdateContainer", typeof(void), typeof(Player));
			MethodInfo methodInfo11 = Method(typeof(InventoryGui), "OnRightClickItem", typeof(void), typeof(InventoryGrid), typeof(ItemData), typeof(Vector2i));
			MethodInfo methodInfo12 = Method(type, "Start", typeof(void));
			MethodInfo methodInfo13 = Method(type2, "Postfix", typeof(void), typeof(Inventory), typeof(ItemData), typeof(bool).MakeByRefType());
			harmony = new Harmony("niclas.smartcontainers.ignore");
			harmony.Patch((MethodBase)methodInfo13, (HarmonyMethod)null, (HarmonyMethod)null, Patch("VerifyRoutingFailure"), (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo3, Patch("BeforeRouting"), (HarmonyMethod)null, Patch("WithoutGroups"), (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, Patch("FilterCandidates"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo2, Patch("BeforeAutomaticAdd"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo9, Patch("SkipGroupButton"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo10, (HarmonyMethod)null, Patch("UpdateButton"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo11, Patch("BeforeRightClick"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo12, (HarmonyMethod)null, Patch("AfterSmartStarted"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, (HarmonyMethod)null, Patch("RouteBeforeOpenChest"), (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, Patch("ObservePriorityAdd"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo6, Patch("BeforeInventoryChanged"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, Patch("AfterContainerSaved"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo8, (HarmonyMethod)null, Patch("AfterContainerLoaded"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			BindGroupingConfig();
			ready = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Ready: shared Auto and Prio buttons; client-only, same-item priority routing and revision-validated contents hints.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Ctrl + right-click: native transfer to/from the open chest without SmartContainers routing.");
		}
		catch (Exception ex)
		{
			ready = false;
			if (groupingGuard != null)
			{
				groupingGuard.Dispose();
			}
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			((BaseUnityPlugin)this).Logger.LogError((object)("SmartContainers Ignore disabled: " + ex));
		}
	}

	private void BindGroupingConfig()
	{
		if (groupingGuard == null && grouping.GetValue(null) is ConfigEntry<bool> entry)
		{
			groupingGuard = new GroupingConfigGuard(entry, delegate(Exception error)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not save disabled grouping; group routing remains disabled: " + error));
			});
			((BaseUnityPlugin)this).Logger.LogInfo((object)"SmartContainers Grouping.enabled is forced off, including config changes and reloads.");
		}
	}

	private static void AfterSmartStarted()
	{
		if (!ready)
		{
			return;
		}
		try
		{
			instance.BindGroupingConfig();
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)instance).Logger.LogWarning((object)("Could not bind grouping config; group routing remains disabled: " + ex));
		}
	}

	private static IEnumerable<CodeInstruction> VerifyRoutingFailure(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod)
	{
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		MethodInfo method = __originalMethod.DeclaringType.GetMethod("TryAddToNearBy", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3]
		{
			typeof(ItemData),
			typeof(bool),
			typeof(bool)
		}, null);
		if (method == null || method.ReturnType != typeof(bool))
		{
			throw new NotSupportedException("SmartContainers routing signature changed.");
		}
		CheckRoutingFailure(list, method);
		return list;
	}

	private static void CheckRoutingFailure(IEnumerable<CodeInstruction> instructions, MethodInfo route)
	{
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		int num = 0;
		for (int i = 0; i < list.Count; i++)
		{
			if (!(list[i].opcode != OpCodes.Call) && object.Equals(list[i].operand, route))
			{
				num++;
				if (i + 1 >= list.Count || (list[i + 1].opcode != OpCodes.Brfalse && list[i + 1].opcode != OpCodes.Brfalse_S))
				{
					throw new NotSupportedException("SmartContainers failed-transfer branch changed.");
				}
			}
		}
		if (num == 0)
		{
			throw new NotSupportedException("SmartContainers routing call missing.");
		}
	}

	private static FieldInfo Field(Type type, string name, Type expected)
	{
		FieldInfo fieldInfo = AccessTools.Field(type, name);
		if (fieldInfo == null || fieldInfo.FieldType != expected)
		{
			throw new MissingFieldException(type.FullName, name);
		}
		return fieldInfo;
	}

	private static MethodInfo Method(Type type, string name, Type result, params Type[] args)
	{
		MethodInfo methodInfo = AccessTools.Method(type, name, args, (Type[])null);
		if (methodInfo == null || methodInfo.ReturnType != result)
		{
			throw new MissingMethodException(type.FullName, name);
		}
		return methodInfo;
	}

	private static HarmonyMethod Patch(string name)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Expected O, but got Unknown
		return new HarmonyMethod(AccessTools.Method(typeof(Plugin), name, (Type[])null, (Type[])null));
	}

	private static IEnumerable<CodeInstruction> WithoutGroups(IEnumerable<CodeInstruction> instructions)
	{
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		MethodInfo getMethod = typeof(ConfigEntry<bool>).GetProperty("Value").GetGetMethod();
		int num = -1;
		for (int i = 0; i < list.Count; i++)
		{
			if (!(list[i].opcode != OpCodes.Ldsfld) && object.Equals(list[i].operand, grouping))
			{
				if (num >= 0 || i + 1 >= list.Count || list[i + 1].opcode != OpCodes.Callvirt || !object.Equals(list[i + 1].operand, getMethod) || list[i].blocks.Count != 0 || list[i + 1].blocks.Count != 0 || list[i + 1].labels.Count != 0)
				{
					throw new NotSupportedException("SmartContainers grouping gate changed.");
				}
				num = i;
			}
		}
		if (num < 0)
		{
			throw new NotSupportedException("SmartContainers grouping gate missing.");
		}
		list[num].opcode = OpCodes.Ldc_I4_0;
		list[num].operand = null;
		list[num + 1].opcode = OpCodes.Nop;
		list[num + 1].operand = null;
		return list;
	}

	private static ZNetView View(Container container)
	{
		if (!Object.op_Implicit((Object)(object)container))
		{
			return null;
		}
		object? value = containerView.GetValue(container);
		return (ZNetView)((value is ZNetView) ? value : null);
	}

	private static IEnumerable<CodeInstruction> RouteBeforeOpenChest(IEnumerable<CodeInstruction> instructions)
	{
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		MethodInfo method = typeof(Inventory).GetMethod("AddItem", new Type[1] { typeof(ItemData) });
		MethodInfo method2 = typeof(Plugin).GetMethod("AddToPriorityOrCurrent", BindingFlags.Static | BindingFlags.NonPublic);
		int num = -1;
		for (int i = 0; i < list.Count; i++)
		{
			if ((!(list[i].opcode != OpCodes.Call) || !(list[i].opcode != OpCodes.Callvirt)) && object.Equals(list[i].operand, method))
			{
				if (num >= 0 || i + 1 >= list.Count || (list[i + 1].opcode != OpCodes.Brfalse && list[i + 1].opcode != OpCodes.Brfalse_S))
				{
					throw new NotSupportedException("Native stack-transfer success branch changed.");
				}
				num = i;
			}
		}
		if (num < 0)
		{
			throw new NotSupportedException("Native stack-transfer AddItem call missing.");
		}
		list[num].opcode = OpCodes.Call;
		list[num].operand = method2;
		return list;
	}

	private static bool RoutingShortcutPressed()
	{
		//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_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		ConfigEntry<KeyboardShortcut> val = keyModifier.GetValue(null) as ConfigEntry<KeyboardShortcut>;
		ConfigEntry<KeyboardShortcut> val2 = groupingModifier.GetValue(null) as ConfigEntry<KeyboardShortcut>;
		ConfigEntry<KeyboardShortcut> val3 = gamepadKey2.GetValue(null) as ConfigEntry<KeyboardShortcut>;
		ConfigEntry<string> val4 = gamepadKey1.GetValue(null) as ConfigEntry<string>;
		if (val != null)
		{
			KeyboardShortcut value = val.Value;
			if (((KeyboardShortcut)(ref value)).IsPressed())
			{
				goto IL_0092;
			}
		}
		if (val2 != null)
		{
			KeyboardShortcut value2 = val2.Value;
			if (((KeyboardShortcut)(ref value2)).IsPressed())
			{
				goto IL_0092;
			}
		}
		if (val4 != null && val3 != null && gamepadPressed(val3.Value))
		{
			return ZInput.GetButton(val4.Value);
		}
		return false;
		IL_0092:
		return true;
	}

	private static bool AnyPriorityTracked()
	{
		if (!(trackedContainers.GetValue(null) is ICollection<Container> collection))
		{
			return false;
		}
		foreach (Container item in collection)
		{
			if (PriorityPolicy.IsPriority(View(item)))
			{
				return true;
			}
		}
		return false;
	}

	private static bool AddToPriorityOrCurrent(Inventory target, ItemData item)
	{
		if (!ready || DirectTransferScope.Active || PriorityTransferScope.Current != null)
		{
			return nativeAdd(target, item);
		}
		bool flag = false;
		try
		{
			Player localPlayer = Player.m_localPlayer;
			Container val = OpenContainer(InventoryGui.instance);
			ConfigEntry<bool> val2 = onlyStackable.GetValue(null) as ConfigEntry<bool>;
			flag = SmartEnabled() && Object.op_Implicit((Object)(object)localPlayer) && !((Character)localPlayer).IsTeleporting() && Object.op_Implicit((Object)(object)val) && item != null && item.m_shared != null && !item.m_shared.m_questItem && object.ReferenceEquals(target, val.GetInventory()) && ((Humanoid)localPlayer).GetInventory().ContainsItem(item) && !(bool)rearranging.GetValue(null) && (val2 == null || !val2.Value || item.m_shared.m_maxStackSize != 1) && RoutingShortcutPressed() && AnyPriorityTracked();
		}
		catch (Exception error)
		{
			RoutingFault(error);
		}
		if (!flag)
		{
			return nativeAdd(target, item);
		}
		if (TryPriorityTransfer(item))
		{
			return true;
		}
		return nativeAdd(target, item);
	}

	private static bool TryPriorityTransfer(ItemData item)
	{
		bool flag = (bool)rearranging.GetValue(null);
		using (PriorityTransferScope priorityTransferScope = new PriorityTransferScope(item))
		{
			try
			{
				rearranging.SetValue(null, true);
				if (routeItems(item, arg2: true, arg3: false) || priorityTransferScope.Committed)
				{
					return true;
				}
			}
			catch (Exception error)
			{
				RoutingFault(error);
				if (priorityTransferScope.Committed)
				{
					return true;
				}
			}
			finally
			{
				rearranging.SetValue(null, flag);
			}
		}
		return false;
	}

	private static void ObservePriorityAdd(Inventory __instance, ItemData __0, bool __result)
	{
		PriorityTransferScope current = PriorityTransferScope.Current;
		if (__result && current != null && object.ReferenceEquals(current.Item, __0) && Object.op_Implicit((Object)(object)Player.m_localPlayer) && !object.ReferenceEquals(__instance, ((Humanoid)Player.m_localPlayer).GetInventory()))
		{
			current.Committed = true;
		}
	}

	private static void BeforeInventoryChanged(Inventory __instance)
	{
		if (!ready || cacheFault)
		{
			return;
		}
		try
		{
			ContentsCache.Changed(__instance);
		}
		catch (Exception error)
		{
			DisableContentsHints(error);
		}
	}

	private static void AfterContainerSaved(Container __instance)
	{
		RefreshContentsHint(__instance, saved: true);
	}

	private static void AfterContainerLoaded(Container __instance, bool __result)
	{
		if (__result)
		{
			RefreshContentsHint(__instance, saved: false);
		}
	}

	private static void RefreshContentsHint(Container container, bool saved)
	{
		if (!ready || cacheFault)
		{
			return;
		}
		try
		{
			ZNetView val = View(container);
			if (!Object.op_Implicit((Object)(object)val) || !val.IsValid())
			{
				return;
			}
			ZDO zDO = val.GetZDO();
			if (zDO == null || (!saved && zDO.GetByteArray(ZDOVars.s_items, (byte[])null) == null))
			{
				return;
			}
			Inventory inventory = container.GetInventory();
			ContentsCache.Fresh(inventory);
			if (val.IsOwner() && zDO.Persistent)
			{
				if (PriorityPolicy.IsPriority(val))
				{
					SingleItemHint.Publish(zDO, ContentsCache.Describe(inventory));
				}
				lastRevision.SetValue(container, zDO.DataRevision);
			}
		}
		catch (Exception error)
		{
			DisableContentsHints(error);
		}
	}

	private static void DisableContentsHints(Exception error)
	{
		cacheFault = true;
		((BaseUnityPlugin)instance).Logger.LogWarning((object)("Contents hints disabled; normal item checks remain active: " + error));
	}

	private static bool CachedRejects(Container container, ZNetView view, ItemData item)
	{
		if (cacheFault)
		{
			return false;
		}
		try
		{
			return ContentsCache.Rejects(container.GetInventory(), view.GetZDO(), item);
		}
		catch (Exception error)
		{
			DisableContentsHints(error);
			return false;
		}
	}

	private static void FilterCandidates(bool __1, ref List<Container> __result)
	{
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Expected O, but got Unknown
		if (__1 || __result == null)
		{
			return;
		}
		try
		{
			__result.RemoveAll((Container c) => ChestPolicy.IsExcluded(View(c)));
			PriorityTransferScope current = PriorityTransferScope.Current;
			List<Container> list = null;
			List<Container> list2 = null;
			foreach (Container item in __result)
			{
				ZNetView view = View(item);
				if (PriorityPolicy.IsPriority(view))
				{
					if (current == null || !CachedRejects(item, view, (ItemData)current.Item))
					{
						if (list == null)
						{
							list = new List<Container>();
						}
						list.Add(item);
					}
				}
				else if (current == null)
				{
					if (list2 == null)
					{
						list2 = new List<Container>();
					}
					list2.Add(item);
				}
			}
			if (current != null || list != null)
			{
				__result.Clear();
				if (list != null)
				{
					__result.AddRange(list);
				}
				if (list2 != null)
				{
					__result.AddRange(list2);
				}
			}
		}
		catch (Exception error)
		{
			__result.Clear();
			RoutingFault(error);
		}
	}

	private static bool BeforeAutomaticAdd(Container __1, ref bool __result)
	{
		try
		{
			if (!ChestPolicy.IsExcluded(View(__1)) && (PriorityTransferScope.Current == null || PriorityPolicy.IsPriority(View(__1))))
			{
				return true;
			}
		}
		catch (Exception error)
		{
			RoutingFault(error);
		}
		__result = false;
		return false;
	}

	private static bool BeforeRouting(ref bool __result)
	{
		if (!DirectTransferScope.Active)
		{
			return true;
		}
		__result = false;
		return false;
	}

	private static bool BeforeRightClick(InventoryGui __instance, InventoryGrid __0, ItemData __1, Vector2i __2)
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		if (!ready || !ZInput.GetMouseButton(1) || (!ZInput.GetKey((KeyCode)306, true) && !ZInput.GetKey((KeyCode)305, true)))
		{
			return true;
		}
		try
		{
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || !Object.op_Implicit((Object)(object)__instance) || (Object)(object)__instance != (Object)(object)InventoryGui.instance || !Object.op_Implicit((Object)(object)__0))
			{
				return true;
			}
			Container val = OpenContainer(__instance);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return true;
			}
			if (__1 == null || __1.m_shared == null || Object.op_Implicit((Object)/*isinst with value type is only supported in some contexts*/))
			{
				return false;
			}
			Inventory inventory = __0.GetInventory();
			Inventory inventory2 = ((Humanoid)localPlayer).GetInventory();
			Inventory inventory3 = val.GetInventory();
			if (inventory == null || inventory3 == null || (!object.ReferenceEquals(inventory, inventory2) && !object.ReferenceEquals(inventory, inventory3)) || !inventory.ContainsItem(__1))
			{
				return false;
			}
			ZNetView val2 = View(val);
			if (!Object.op_Implicit((Object)(object)val2) || !val2.IsValid() || !val2.IsOwner() || !checkAccess(val, localPlayer.GetPlayerID()))
			{
				return false;
			}
			DirectTransferScope.Run(delegate
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				selectItem(__instance, __0, __1, __2, (Modifier)2);
			});
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)instance).Logger.LogWarning((object)("Direct chest transfer stopped: " + ex));
			Notice("Direkter Transfer abgebrochen. Siehe BepInEx-Log.");
		}
		return false;
	}

	private static void RoutingFault(Exception error)
	{
		if (!routingFaultLogged)
		{
			routingFaultLogged = true;
			((BaseUnityPlugin)instance).Logger.LogError((object)("Automatic routing compatibility check failed: " + error));
		}
	}

	private static bool SkipGroupButton()
	{
		return false;
	}

	private static Container OpenContainer(InventoryGui gui)
	{
		if (!Object.op_Implicit((Object)(object)gui) || !gui.IsContainerOpen())
		{
			return null;
		}
		object? value = currentContainer.GetValue(gui);
		return (Container)((value is Container) ? value : null);
	}

	private static bool SmartEnabled()
	{
		if (master.GetValue(null) is ConfigEntry<bool> val)
		{
			return val.Value;
		}
		return false;
	}

	private static void UpdateButton(InventoryGui __instance)
	{
		//IL_0134: Unknown result type (might be due to invalid IL or missing references)
		//IL_011e: Unknown result type (might be due to invalid IL or missing references)
		//IL_020d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
		if (!ready || uiFault)
		{
			return;
		}
		try
		{
			Container val = OpenContainer(__instance);
			if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !SmartEnabled())
			{
				if (Object.op_Implicit((Object)(object)button))
				{
					((Component)button).gameObject.SetActive(false);
				}
				if (Object.op_Implicit((Object)(object)priorityButton))
				{
					((Component)priorityButton).gameObject.SetActive(false);
				}
				return;
			}
			if (!Object.op_Implicit((Object)(object)button))
			{
				MakeButton(__instance);
			}
			if (!Object.op_Implicit((Object)(object)priorityButton))
			{
				MakePriorityButton(__instance);
			}
			((Component)button).gameObject.SetActive(true);
			ZNetView view = View(val);
			bool flag = ChestPolicy.IsExcluded(view);
			bool hasAccess = checkAccess(val, Player.m_localPlayer.GetPlayerID());
			((Selectable)button).interactable = ChestPolicy.CanToggle(view, hasAccess);
			string text = (flag ? "Auto\nAUS" : "Auto\nAN");
			if (label.text != text)
			{
				label.text = text;
			}
			((Graphic)label).color = (flag ? new Color(1f, 0.55f, 0.35f) : new Color(0.65f, 1f, 0.65f));
			if (Object.op_Implicit((Object)(object)tooltip))
			{
				tooltip.m_topic = "SmartContainers";
				tooltip.m_text = ((!((Selectable)button).interactable) ? "Truhe noch nicht bereit oder kein Zugriff. Erneut oeffnen." : (flag ? "Diese Truhe wird ignoriert. Klicken: automatisches Einsortieren wieder zulassen." : "Klicken: diese Truhe vom automatischen Einsortieren ausschliessen. Gilt fuer alle mit diesem Zusatz."));
			}
			((Component)priorityButton).gameObject.SetActive(!flag);
			((Selectable)priorityButton).interactable = ((Selectable)button).interactable && !flag;
			bool flag2 = PriorityPolicy.IsPriority(view);
			string text2 = (flag2 ? "Prio\nAN" : "Prio\nAUS");
			if (priorityLabel.text != text2)
			{
				priorityLabel.text = text2;
			}
			((Graphic)priorityLabel).color = (Color)(flag2 ? new Color(1f, 0.85f, 0.35f) : Color.white);
			if (Object.op_Implicit((Object)(object)priorityTooltip))
			{
				priorityTooltip.m_topic = "SmartContainers Prioritaet";
				priorityTooltip.m_text = ((!((Selectable)priorityButton).interactable) ? "Truhe noch nicht bereit oder kein Zugriff. Erneut oeffnen." : "Passende Stapel zuerst hier einraeumen, sofern Platz ist. Gilt fuer alle mit diesem Zusatz. Klicken: Prioritaet umschalten.");
			}
		}
		catch (Exception ex)
		{
			uiFault = true;
			if (Object.op_Implicit((Object)(object)button))
			{
				((Component)button).gameObject.SetActive(false);
			}
			if (Object.op_Implicit((Object)(object)priorityButton))
			{
				((Component)priorityButton).gameObject.SetActive(false);
			}
			((BaseUnityPlugin)instance).Logger.LogError((object)("Exclusion button unavailable; saved exclusions still apply: " + ex));
		}
	}

	private static void MakeButton(InventoryGui gui)
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Expected O, but got Unknown
		button = CreateContainerButton(gui, "SmartContainersIgnoreButton", "Auto\nAN", 0f, new UnityAction(ToggleCurrent), out label, out tooltip);
	}

	private static void MakePriorityButton(InventoryGui gui)
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Expected O, but got Unknown
		priorityButton = CreateContainerButton(gui, "SmartContainersPriorityButton", "Prio\nAUS", 50f, new UnityAction(TogglePriority), out priorityLabel, out priorityTooltip);
	}

	private static Button CreateContainerButton(InventoryGui gui, string name, string text, float offset, UnityAction click, out TMP_Text textLabel, out UITooltip tip)
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_003d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: 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)
		//IL_0065: 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_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Expected O, but got Unknown
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_014f: Unknown result type (might be due to invalid IL or missing references)
		Vector2 arg = (Vector2)((!(buttonPosition.GetValue(null) is ConfigEntry<Vector2> val)) ? new Vector2(1.5f, 10.7f) : val.Value);
		Transform val2 = createButton(name, text, new Vector2(45f, 30f), arg);
		RectTransform component = ((Component)val2).GetComponent<RectTransform>();
		if (Object.op_Implicit((Object)(object)component))
		{
			component.anchoredPosition += new Vector2(offset, 0f);
		}
		Button component2 = ((Component)val2).GetComponent<Button>();
		if (!Object.op_Implicit((Object)(object)component2))
		{
			throw new MissingComponentException("Container button template has no Button.");
		}
		component2.onClick = new ButtonClickedEvent();
		((UnityEvent)component2.onClick).AddListener(click);
		UIGamePad[] componentsInChildren = ((Component)val2).GetComponentsInChildren<UIGamePad>(true);
		foreach (UIGamePad val3 in componentsInChildren)
		{
			((Behaviour)val3).enabled = false;
			if (Object.op_Implicit((Object)(object)val3.m_hint))
			{
				val3.m_hint.SetActive(false);
			}
		}
		Transform val4 = val2.Find("gamepad_hint");
		if (Object.op_Implicit((Object)(object)val4))
		{
			((Component)val4).gameObject.SetActive(false);
		}
		val4 = val2.Find("GamepadHint");
		if (Object.op_Implicit((Object)(object)val4))
		{
			((Component)val4).gameObject.SetActive(false);
		}
		textLabel = ((Component)val2).GetComponentInChildren<TMP_Text>(true);
		if (!Object.op_Implicit((Object)(object)textLabel))
		{
			throw new MissingComponentException("Container button template has no TMP text.");
		}
		textLabel.fontSize = 11f;
		textLabel.enableAutoSizing = true;
		textLabel.fontSizeMin = 8f;
		textLabel.fontSizeMax = 11f;
		textLabel.alignment = (TextAlignmentOptions)514;
		tip = ((Component)val2).GetComponent<UITooltip>();
		if (!Object.op_Implicit((Object)(object)tip))
		{
			UITooltip componentInChildren = ((Component)gui).GetComponentInChildren<UITooltip>(true);
			if (Object.op_Implicit((Object)(object)componentInChildren) && Object.op_Implicit((Object)(object)componentInChildren.m_tooltipPrefab))
			{
				tip = ((Component)val2).gameObject.AddComponent<UITooltip>();
				tip.m_tooltipPrefab = componentInChildren.m_tooltipPrefab;
			}
		}
		return component2;
	}

	private static void ToggleCurrent()
	{
		try
		{
			if (!ready || !SmartEnabled())
			{
				return;
			}
			InventoryGui val = InventoryGui.instance;
			Container val2 = OpenContainer(val);
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)localPlayer))
			{
				return;
			}
			ZNetView val3 = View(val2);
			bool flag = Object.op_Implicit((Object)(object)val3) && val3.IsValid() && val3.GetZDO() != null && (uint)lastRevision.GetValue(val2) == val3.GetZDO().DataRevision && !ContentsCache.IsDirty(val2.GetInventory());
			if (!ChestPolicy.TryToggle(View(val2), checkAccess(val2, localPlayer.GetPlayerID()), out var excluded))
			{
				Notice("Truhe noch nicht bereit oder kein Zugriff. Bitte erneut oeffnen.");
				return;
			}
			Notice(excluded ? "SmartContainers: Diese Truhe wird ignoriert." : "SmartContainers: Diese Truhe ist wieder zugelassen.");
			if (flag)
			{
				RefreshContentsHint(val2, saved: true);
			}
			UpdateButton(val);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)instance).Logger.LogWarning((object)("Chest exclusion could not be changed: " + ex));
			Notice("Truhen-Sperre konnte nicht geaendert werden. Siehe BepInEx-Log.");
		}
	}

	private static void TogglePriority()
	{
		try
		{
			if (!ready || !SmartEnabled())
			{
				return;
			}
			InventoryGui val = InventoryGui.instance;
			Container val2 = OpenContainer(val);
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)localPlayer))
			{
				return;
			}
			ZNetView val3 = View(val2);
			bool flag = Object.op_Implicit((Object)(object)val3) && val3.IsValid() && val3.GetZDO() != null && (uint)lastRevision.GetValue(val2) == val3.GetZDO().DataRevision && !ContentsCache.IsDirty(val2.GetInventory());
			if (!PriorityPolicy.TryToggle(val3, checkAccess(val2, localPlayer.GetPlayerID()), out var priority))
			{
				Notice("Truhe noch nicht bereit, Auto AUS oder kein Zugriff. Bitte erneut oeffnen.");
				return;
			}
			if (flag)
			{
				RefreshContentsHint(val2, saved: true);
			}
			Notice(priority ? "SmartContainers: Diese Truhe hat Prioritaet." : "SmartContainers: Prioritaet ausgeschaltet.");
			UpdateButton(val);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)instance).Logger.LogWarning((object)("Chest priority could not be changed: " + ex));
			Notice("Prioritaet konnte nicht geaendert werden. Siehe BepInEx-Log.");
		}
	}

	private static void Notice(string text)
	{
		if (Object.op_Implicit((Object)(object)MessageHud.instance))
		{
			MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true);
		}
	}

	private void OnDestroy()
	{
		ready = false;
		if (groupingGuard != null)
		{
			groupingGuard.Dispose();
		}
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		if (Object.op_Implicit((Object)(object)button))
		{
			Object.Destroy((Object)(object)((Component)button).gameObject);
		}
		if (Object.op_Implicit((Object)(object)priorityButton))
		{
			Object.Destroy((Object)(object)((Component)priorityButton).gameObject);
		}
	}
}
internal static class PriorityPolicy
{
	internal const string Key = "niclas.smartcontainers.priority.v1";

	internal static bool IsPriority(ZNetView view)
	{
		if (!ChestPolicy.IsExcluded(view))
		{
			return view.GetZDO().GetBool("niclas.smartcontainers.priority.v1", false);
		}
		return false;
	}

	internal static bool TryToggle(ZNetView view, bool access, out bool priority)
	{
		priority = false;
		if (!ChestPolicy.CanToggle(view, access) || ChestPolicy.IsExcluded(view))
		{
			return false;
		}
		ZDO zDO = view.GetZDO();
		priority = !zDO.GetBool("niclas.smartcontainers.priority.v1", false);
		zDO.Set("niclas.smartcontainers.priority.v1", priority);
		return true;
	}
}
internal sealed class PriorityTransferScope : IDisposable
{
	[ThreadStatic]
	private static PriorityTransferScope current;

	private readonly PriorityTransferScope previous;

	internal bool Committed;

	internal readonly object Item;

	internal static PriorityTransferScope Current => current;

	internal PriorityTransferScope(object item)
	{
		Item = item;
		previous = current;
		current = this;
	}

	public void Dispose()
	{
		current = previous;
	}
}
internal sealed class SingleItemHint
{
	internal const string HintKey = "niclas.smartcontainers.single.v1";

	internal const string RevisionKey = "niclas.smartcontainers.single.revision.v1";

	private string encoded;

	private string name;

	private int maxWorldLevel;

	internal static string Encode(string name, int maxWorldLevel)
	{
		if (!string.IsNullOrEmpty(name))
		{
			return maxWorldLevel.ToString(CultureInfo.InvariantCulture) + "|" + name;
		}
		return string.Empty;
	}

	internal static void Publish(ZDO data, string hint)
	{
		if (!(data.GetString("niclas.smartcontainers.single.v1", "") == hint) || data.GetLong("niclas.smartcontainers.single.revision.v1", -1L) != data.DataRevision)
		{
			data.Set("niclas.smartcontainers.single.v1", hint);
			data.Set("niclas.smartcontainers.single.revision.v1", (long)(data.DataRevision + 1));
		}
	}

	internal bool Rejects(ZDO data, bool dirty, string incomingName, int worldLevel)
	{
		if (dirty || data == null || data.GetLong("niclas.smartcontainers.single.revision.v1", -1L) != data.DataRevision)
		{
			return false;
		}
		string text = data.GetString("niclas.smartcontainers.single.v1", "") ?? "";
		if (!string.Equals(encoded, text, StringComparison.Ordinal))
		{
			encoded = text;
			name = null;
			int num = text.IndexOf('|');
			if (num > 0 && num + 1 < text.Length && int.TryParse(text.Substring(0, num), NumberStyles.Integer, CultureInfo.InvariantCulture, out maxWorldLevel))
			{
				name = text.Substring(num + 1);
			}
		}
		if (name != null)
		{
			if (!(name != incomingName))
			{
				return maxWorldLevel < worldLevel;
			}
			return true;
		}
		return false;
	}
}