Decompiled source of Safe Pouch v0.7.2

BepInEx/plugins/SafePouch/SafePouch.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("SafePouch")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.7.2.0")]
[assembly: AssemblyInformationalVersion("0.7.2")]
[assembly: AssemblyProduct("SafePouch")]
[assembly: AssemblyTitle("SafePouch")]
[assembly: AssemblyVersion("0.7.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 SafePouch
{
	[BepInPlugin("local.valheim.safepouch", "Safe Pouch", "0.7.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(Player), "Load")]
		private static class LoadPatch
		{
			private static void Postfix(Player __instance)
			{
				Instance._states.Remove(__instance);
			}
		}

		[HarmonyPatch(typeof(Player), "Save")]
		private static class SavePatch
		{
			private static void Prefix(Player __instance)
			{
				if (Instance._states.TryGetValue(__instance, out PouchState value))
				{
					value.Store();
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "GetTotalWeight")]
		private static class WeightPatch
		{
			private static void Postfix(Inventory __instance, ref float __result)
			{
				Player localPlayer = Player.m_localPlayer;
				if (Object.op_Implicit((Object)(object)localPlayer) && __instance == ((Humanoid)localPlayer).GetInventory())
				{
					__result += Instance.State(localPlayer).Inventory.GetAllItems().Sum((ItemData i) => i.GetWeight(-1)) * ((float)Instance.EffectiveWeightPercent / 100f);
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "OnSelectedItem")]
		private static class SelectedPatch
		{
			private static bool Prefix(InventoryGui __instance, InventoryGrid grid, Vector2i pos)
			{
				//IL_005b: 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)
				PouchState current = Current;
				if (current == null || Drag.Inventory(__instance) != current.Inventory)
				{
					return true;
				}
				Inventory inventory = grid.GetInventory();
				if (inventory != ((Humanoid)Player.m_localPlayer).GetInventory())
				{
					((Character)Player.m_localPlayer).Message((MessageType)2, "Move pouch items into your inventory first.", 0, (Sprite)null, false);
					return false;
				}
				ItemData val = Drag.Item(__instance);
				if (val != null)
				{
					current.Transfer(current.Inventory, inventory, val, Drag.Amount(__instance), pos.x, pos.y);
				}
				Drag.Clear(__instance);
				return false;
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "UpdateContainer")]
		private static class ContainerUpdatePatch
		{
			private static void Prefix(ref Inventory? ___m_dragInventory, out Inventory? __state)
			{
				__state = null;
				PouchState current = Current;
				if (current != null && ___m_dragInventory == current.Inventory)
				{
					__state = ___m_dragInventory;
					___m_dragInventory = null;
				}
			}

			private static void Postfix(ref Inventory? ___m_dragInventory, ItemData? ___m_dragItem, Inventory? __state)
			{
				if (__state != null && ___m_dragItem != null && ___m_dragInventory == null)
				{
					___m_dragInventory = __state;
				}
			}
		}

		[HarmonyPatch(typeof(InventoryGui), "OnDropOutside")]
		private static class OutsidePatch
		{
			private static bool Prepare()
			{
				return AccessTools.Method(typeof(InventoryGui), "OnDropOutside", (Type[])null, (Type[])null) != null;
			}

			private static bool Prefix(InventoryGui __instance)
			{
				PouchState current = Current;
				if (current == null || Drag.Inventory(__instance) != current.Inventory)
				{
					return true;
				}
				Drag.Clear(__instance);
				return false;
			}
		}

		public const string Id = "local.valheim.safepouch";

		internal static Plugin Instance;

		private Harmony _harmony;

		private PouchUi? _ui;

		private bool _uiFailed;

		internal ConfigEntry<float> PouchGap;

		internal ConfigEntry<float> VerticalOffset;

		internal ConfigEntry<int> WeightPercent;

		internal ConfigEntry<int> SlotCount;

		internal ConfigEntry<int> RegularStackLimit;

		internal ConfigEntry<int> PortalRestrictedStackLimit;

		internal ConfigEntry<bool> BlockPortalWithRestrictedItems;

		internal PouchConfigSync Sync;

		private readonly ConditionalWeakTable<Player, PouchState> _states = new ConditionalWeakTable<Player, PouchState>();

		internal int EffectiveSlotCount => Sync.Effective.Slots;

		internal int EffectiveWeightPercent => Sync.Effective.WeightPercent;

		internal static PouchState? Current
		{
			get
			{
				if (!Object.op_Implicit((Object)(object)Player.m_localPlayer))
				{
					return null;
				}
				return Instance.State(Player.m_localPlayer);
			}
		}

		private void Awake()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Expected O, but got Unknown
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Expected O, but got Unknown
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Expected O, but got Unknown
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Expected O, but got Unknown
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Expected O, but got Unknown
			Instance = this;
			SlotCount = ((BaseUnityPlugin)this).Config.Bind<int>("Pouch", "SlotCount", 4, new ConfigDescription("Number of pouch slots (1-28), wrapping after seven per row. Servers with this mod override this setting while connected. Occupied slots beyond a reduced count remain withdrawal-only until emptied.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 28), Array.Empty<object>()));
			WeightPercent = ((BaseUnityPlugin)this).Config.Bind<int>("Pouch", "WeightPercent", 100, new ConfigDescription("Percentage of normal item weight counted while inside the pouch: 0 = weightless, 50 = half weight, 100 = normal weight. Servers with this mod override this setting while connected. Withdrawn items always use normal weight.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			RegularStackLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Pouch", "RegularStackLimit", 99999, new ConfigDescription("Maximum items per pouch slot for teleportable items that normally stack (1-99999). Non-stackable gear stays one per slot. Servers override this setting while connected. Existing larger stacks are preserved for withdrawal.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 99999), Array.Empty<object>()));
			PortalRestrictedStackLimit = ((BaseUnityPlugin)this).Config.Bind<int>("Pouch", "PortalRestrictedStackLimit", 1, new ConfigDescription("Maximum items per pouch slot for portal-restricted items that normally stack (1-99999). Non-stackable gear stays one per slot. Servers override this setting while connected. Existing larger stacks are preserved for withdrawal.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 99999), Array.Empty<object>()));
			BlockPortalWithRestrictedItems = ((BaseUnityPlugin)this).Config.Bind<bool>("Pouch", "BlockPortalWithRestrictedItems", false, "Apply normal portal restrictions to pouch contents. False lets pouch items pass through portals; true blocks ordinary portals when restricted items are in the pouch. Portals and world settings that allow all items still work. Servers override this setting while connected.");
			PouchGap = ((BaseUnityPlugin)this).Config.Bind<float>("Interface", "Gap", 8f, new ConfigDescription("Space below the visible inventory panel in UI units.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f), Array.Empty<object>()));
			VerticalOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Interface", "VerticalOffset", 0f, new ConfigDescription("Additional pouch offset in UI units. Positive moves down; negative moves up.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-500f, 500f), Array.Empty<object>()));
			Sync = new PouchConfigSync(() => new PouchPolicy(SlotCount.Value, WeightPercent.Value, RegularStackLimit.Value, PortalRestrictedStackLimit.Value, BlockPortalWithRestrictedItems.Value), delegate(string message)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)message);
			});
			SlotCount.SettingChanged += OnPouchSettingsChanged;
			WeightPercent.SettingChanged += OnPouchSettingsChanged;
			RegularStackLimit.SettingChanged += OnPouchSettingsChanged;
			PortalRestrictedStackLimit.SettingChanged += OnPouchSettingsChanged;
			BlockPortalWithRestrictedItems.SettingChanged += OnPouchSettingsChanged;
			_harmony = new Harmony("local.valheim.safepouch");
			_harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Safe Pouch 0.7.2 loaded: personal pouch with optional server-authoritative settings.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"Pouch config: {((BaseUnityPlugin)this).Config.ConfigFilePath} ({SlotCount.Value} slots, {WeightPercent.Value}% weight, regular stack limit {RegularStackLimit.Value}, portal-restricted stack limit {PortalRestrictedStackLimit.Value}, block portals with restricted items {BlockPortalWithRestrictedItems.Value}).");
		}

		internal PouchState State(Player player)
		{
			PouchPolicy policy = Sync.Effective;
			PouchState value = _states.GetValue(player, (Player p) => new PouchState(p, policy.Slots, policy.RegularStackLimit, policy.PortalRestrictedStackLimit));
			value.SetSlotCount(policy.Slots);
			value.SetStackLimits(policy.RegularStackLimit, policy.PortalRestrictedStackLimit);
			return value;
		}

		private void OnPouchSettingsChanged(object sender, EventArgs args)
		{
			Sync.SettingsChanged();
		}

		private void Update()
		{
			Sync.Tick(ZNet.instance);
		}

		internal void Report(Exception error)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)error);
		}

		internal static void InventoryChanged(Inventory inventory)
		{
			AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null).Invoke(inventory, new object[2] { false, false });
		}

		private void LateUpdate()
		{
			if (_uiFailed || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)InventoryGui.instance))
			{
				return;
			}
			try
			{
				PouchState pouchState = State(Player.m_localPlayer);
				pouchState.RestoreEquipment();
				if (_ui == null || !_ui.Alive || _ui.SlotCount != pouchState.VisibleSlots)
				{
					_ui?.Destroy();
					_ui = new PouchUi(InventoryGui.instance, pouchState.VisibleSlots);
				}
				_ui.Refresh(pouchState);
			}
			catch (Exception error)
			{
				_uiFailed = true;
				_ui?.Destroy();
				Report(error);
				((BaseUnityPlugin)this).Logger.LogError((object)"Pouch UI disabled for this session. Saved contents are retained.");
			}
		}

		private void OnDestroy()
		{
			SlotCount.SettingChanged -= OnPouchSettingsChanged;
			WeightPercent.SettingChanged -= OnPouchSettingsChanged;
			RegularStackLimit.SettingChanged -= OnPouchSettingsChanged;
			PortalRestrictedStackLimit.SettingChanged -= OnPouchSettingsChanged;
			BlockPortalWithRestrictedItems.SettingChanged -= OnPouchSettingsChanged;
			Sync?.Reset();
			_ui?.Destroy();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class Drag
	{
		internal static ItemData? Item(InventoryGui gui)
		{
			return Traverse.Create((object)gui).Field("m_dragItem").GetValue<ItemData>();
		}

		internal static Inventory? Inventory(InventoryGui gui)
		{
			return Traverse.Create((object)gui).Field("m_dragInventory").GetValue<Inventory>();
		}

		internal static int Amount(InventoryGui gui)
		{
			return Traverse.Create((object)gui).Field("m_dragAmount").GetValue<int>();
		}

		internal static void Set(InventoryGui gui, ItemData? item, Inventory? inventory, int amount)
		{
			AccessTools.Method(typeof(InventoryGui), "SetupDragItem", (Type[])null, (Type[])null).Invoke(gui, new object[3] { item, inventory, amount });
		}

		internal static void Clear(InventoryGui gui)
		{
			Set(gui, null, null, 1);
		}
	}
	internal sealed class PouchConfigSync
	{
		[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
		private static class NewConnectionPatch
		{
			private static void Prefix(ZNet __instance, ZNetPeer peer)
			{
				Plugin.Instance.Sync.RegisterPeer(__instance, peer);
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		private static class PeerReadyPatch
		{
			private static void Postfix(ZNet __instance, ZRpc rpc)
			{
				Plugin.Instance.Sync.PeerReady(__instance, rpc);
			}
		}

		[HarmonyPatch(typeof(ZNet), "Disconnect")]
		private static class DisconnectPatch
		{
			private static void Prefix(ZNet __instance, ZNetPeer peer)
			{
				Plugin.Instance.Sync.Disconnect(__instance, peer);
			}
		}

		[HarmonyPatch(typeof(ZNet), "Shutdown")]
		private static class ShutdownPatch
		{
			private static void Prefix(ZNet __instance)
			{
				Plugin.Instance.Sync.Shutdown(__instance);
			}
		}

		private const string ConfigRpc = "local.valheim.safepouch.Config.v1";

		private const string AckRpc = "local.valheim.safepouch.ConfigAck.v1";

		private readonly Func<PouchPolicy> _localPolicy;

		private readonly Action<string> _log;

		private readonly HashSet<ZRpc> _subscribers = new HashSet<ZRpc>();

		private ZNet? _session;

		private ZRpc? _authority;

		private PouchPolicy? _remotePolicy;

		private ZRpc? _pendingServer;

		private PouchPolicy? _pendingPolicy;

		private volatile bool _broadcastPending;

		internal PouchPolicy Effective => _remotePolicy ?? _localPolicy();

		internal bool ServerControlled => _remotePolicy.HasValue;

		internal PouchConfigSync(Func<PouchPolicy> localPolicy, Action<string> log)
		{
			_localPolicy = localPolicy;
			_log = log;
		}

		internal void SettingsChanged()
		{
			_broadcastPending = true;
		}

		internal void RegisterPeer(ZNet net, ZNetPeer peer)
		{
			if (_session != net)
			{
				Reset();
				_session = net;
			}
			if (net.IsServer())
			{
				peer.m_rpc.Register<int>("local.valheim.safepouch.ConfigAck.v1", (Action<ZRpc, int>)ReceiveAck);
				return;
			}
			if (peer.m_server)
			{
				ClearRemote();
			}
			peer.m_rpc.Register<ZPackage>("local.valheim.safepouch.Config.v1", (Action<ZRpc, ZPackage>)ReceiveConfig);
		}

		internal void PeerReady(ZNet net, ZRpc rpc)
		{
			if (net == _session && net.IsServer() && ReadyPeer(net, rpc) != null)
			{
				PouchPolicy policy = _localPolicy();
				_log("Offering server pouch settings: " + Describe(policy) + ".");
				SendPolicy(rpc);
			}
		}

		private static ZNetPeer? ReadyPeer(ZNet net, ZRpc rpc)
		{
			return net.GetPeers().Find((ZNetPeer p) => p.m_rpc == rpc && p.IsReady());
		}

		private void SendPolicy(ZRpc rpc)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			PouchPolicy pouchPolicy = _localPolicy();
			ZPackage val = new ZPackage();
			val.Write(3);
			val.Write(pouchPolicy.Slots);
			val.Write(pouchPolicy.WeightPercent);
			val.Write(pouchPolicy.RegularStackLimit);
			val.Write(pouchPolicy.PortalRestrictedStackLimit);
			val.Write(pouchPolicy.BlockPortalWithRestrictedItems ? 1 : 0);
			rpc.Invoke("local.valheim.safepouch.Config.v1", new object[1] { val });
		}

		private void ReceiveAck(ZRpc rpc, int protocol)
		{
			if (Object.op_Implicit((Object)(object)_session) && _session.IsServer() && protocol == 3 && ReadyPeer(_session, rpc) != null && _subscribers.Add(rpc))
			{
				_log("Client acknowledged server pouch settings.");
			}
		}

		private void ReceiveConfig(ZRpc rpc, ZPackage package)
		{
			if (!Object.op_Implicit((Object)(object)_session) || _session.IsServer() || !_session.GetPeers().Exists((ZNetPeer p) => p.m_rpc == rpc && p.m_server))
			{
				return;
			}
			try
			{
				if (package.Size() != 24 || package.ReadInt() != 3)
				{
					throw new FormatException("Unsupported pouch config packet.");
				}
				int slots = package.ReadInt();
				int weightPercent = package.ReadInt();
				int regularStackLimit = package.ReadInt();
				int portalRestrictedStackLimit = package.ReadInt();
				int num = package.ReadInt();
				if (num != 0 && num != 1)
				{
					throw new FormatException("Invalid portal restriction flag.");
				}
				PouchPolicy pouchPolicy = new PouchPolicy(slots, weightPercent, regularStackLimit, portalRestrictedStackLimit, num == 1);
				if (rpc == _session.GetServerRPC() && ReadyPeer(_session, rpc) != null)
				{
					ApplyPolicy(rpc, pouchPolicy);
					return;
				}
				_pendingServer = rpc;
				_pendingPolicy = pouchPolicy;
				_log("Received server pouch settings; waiting for login to complete.");
			}
			catch (Exception ex)
			{
				_log("Ignored invalid server pouch settings: " + ex.Message);
			}
		}

		private void ApplyPolicy(ZRpc rpc, PouchPolicy policy)
		{
			bool num = !_remotePolicy.HasValue || _remotePolicy.Value.Slots != policy.Slots || _remotePolicy.Value.WeightPercent != policy.WeightPercent || _remotePolicy.Value.RegularStackLimit != policy.RegularStackLimit || _remotePolicy.Value.PortalRestrictedStackLimit != policy.PortalRestrictedStackLimit || _remotePolicy.Value.BlockPortalWithRestrictedItems != policy.BlockPortalWithRestrictedItems;
			_pendingServer = null;
			_pendingPolicy = null;
			_authority = rpc;
			_remotePolicy = policy;
			if (num)
			{
				_log("Server pouch settings applied: " + Describe(policy) + ". Local config file preserved.");
			}
			rpc.Invoke("local.valheim.safepouch.ConfigAck.v1", new object[1] { 3 });
		}

		private static string Describe(PouchPolicy policy)
		{
			return $"{policy.Slots} slots, {policy.WeightPercent}% weight, regular stack limit {policy.RegularStackLimit}, portal-restricted stack limit {policy.PortalRestrictedStackLimit}, block portals with restricted items {policy.BlockPortalWithRestrictedItems}";
		}

		internal void Tick(ZNet? net)
		{
			if (!Object.op_Implicit((Object)(object)net) || net != _session)
			{
				Reset();
				_session = net;
			}
			if (!Object.op_Implicit((Object)(object)net))
			{
				return;
			}
			if (_authority != null && _authority != net.GetServerRPC())
			{
				ClearRemote();
			}
			if (_pendingServer != null && _pendingPolicy.HasValue)
			{
				if (!net.GetPeers().Exists((ZNetPeer p) => p.m_rpc == _pendingServer && p.m_server))
				{
					_pendingServer = null;
					_pendingPolicy = null;
				}
				else if (!net.IsServer() && _pendingServer == net.GetServerRPC() && ReadyPeer(net, _pendingServer) != null)
				{
					ApplyPolicy(_pendingServer, _pendingPolicy.Value);
				}
			}
			if (!_broadcastPending)
			{
				return;
			}
			_broadcastPending = false;
			if (!net.IsServer())
			{
				return;
			}
			foreach (ZNetPeer peer in net.GetPeers())
			{
				if (peer.IsReady() && _subscribers.Contains(peer.m_rpc))
				{
					SendPolicy(peer.m_rpc);
				}
			}
		}

		internal void Disconnect(ZNet net, ZNetPeer peer)
		{
			if (net == _session)
			{
				_subscribers.Remove(peer.m_rpc);
				if (peer.m_rpc == _authority || peer.m_rpc == _pendingServer)
				{
					ClearRemote();
				}
			}
		}

		internal void Shutdown(ZNet net)
		{
			if (net == _session)
			{
				Reset();
			}
		}

		private void ClearRemote()
		{
			if (_remotePolicy.HasValue)
			{
				_log("Server pouch settings released; local settings restored.");
			}
			_authority = null;
			_remotePolicy = null;
			_pendingServer = null;
			_pendingPolicy = null;
		}

		internal void Reset()
		{
			ClearRemote();
			_session = null;
			_subscribers.Clear();
			_broadcastPending = false;
		}
	}
	internal static class PouchEquipment
	{
		[HarmonyPatch(typeof(FejdStartup), "SetupCharacterPreview")]
		private static class PreviewEquipmentPatch
		{
			private static void Postfix(FejdStartup __instance)
			{
				Player previewPlayer = __instance.GetPreviewPlayer();
				if (!Object.op_Implicit((Object)(object)previewPlayer))
				{
					return;
				}
				Inventory previewInventory = _previewInventory;
				PouchState previewState = _previewState;
				try
				{
					_previewInventory = ((Humanoid)previewPlayer).GetInventory();
					_previewState = new PouchState(previewPlayer, Plugin.Instance.SlotCount.Value);
					_previewState.RestoreEquipment();
				}
				catch (Exception error)
				{
					Plugin.Instance.Report(error);
				}
				finally
				{
					_previewInventory = previewInventory;
					_previewState = previewState;
				}
			}
		}

		[HarmonyPatch(typeof(Humanoid), "EquipItem")]
		private static class EquipOwnershipPatch
		{
			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				MethodInfo original = AccessTools.Method(typeof(Inventory), "ContainsItem", (Type[])null, (Type[])null);
				foreach (CodeInstruction instruction in instructions)
				{
					if (CodeInstructionExtensions.Calls(instruction, original))
					{
						instruction.opcode = OpCodes.Call;
						instruction.operand = AccessTools.Method(typeof(PouchEquipment), "OwnsEquipment", (Type[])null, (Type[])null);
					}
					yield return instruction;
				}
			}
		}

		[HarmonyPatch]
		private static class EquipmentEffectsPatch
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				yield return AccessTools.Method(typeof(Humanoid), "UpdateEquipment", (Type[])null, (Type[])null);
				yield return AccessTools.Method(typeof(Player), "AddAdrenaline", (Type[])null, (Type[])null);
			}

			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				MethodInfo original = AccessTools.Method(typeof(Inventory), "GetAllItems", (Type[])null, (Type[])null);
				foreach (CodeInstruction instruction in instructions)
				{
					if (CodeInstructionExtensions.Calls(instruction, original))
					{
						instruction.opcode = OpCodes.Call;
						instruction.operand = AccessTools.Method(typeof(PouchEquipment), "EquipmentItems", (Type[])null, (Type[])null);
					}
					yield return instruction;
				}
			}
		}

		[HarmonyPatch]
		private static class AttackAmmoOwnershipPatch
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				string[] array = new string[4] { "FindAmmo", "EquipAmmoItem", "HaveAmmo", "UseAmmo" };
				foreach (string text in array)
				{
					yield return AccessTools.Method(typeof(Attack), text, (Type[])null, (Type[])null);
				}
			}

			private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
			{
				MethodInfo original = AccessTools.Method(typeof(Inventory), "ContainsItem", (Type[])null, (Type[])null);
				foreach (CodeInstruction instruction in instructions)
				{
					if (CodeInstructionExtensions.Calls(instruction, original))
					{
						instruction.opcode = OpCodes.Call;
						instruction.operand = AccessTools.Method(typeof(PouchInventory), "ContainsForUse", (Type[])null, (Type[])null);
					}
					yield return instruction;
				}
			}
		}

		private static Inventory? _previewInventory;

		private static PouchState? _previewState;

		private static PouchState? For(Inventory inventory)
		{
			if (inventory == _previewInventory)
			{
				PouchState previewState = _previewState;
				if (previewState == null || previewState.Locked)
				{
					return null;
				}
				return _previewState;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || inventory != ((Humanoid)localPlayer).GetInventory())
			{
				return null;
			}
			PouchState current = Plugin.Current;
			if (current == null || current.Locked)
			{
				return null;
			}
			return current;
		}

		private static bool OwnsEquipment(Inventory inventory, ItemData item)
		{
			if (!inventory.ContainsItem(item))
			{
				if (PouchState.CanSelectEquipment(item))
				{
					PouchState? pouchState = For(inventory);
					if (pouchState == null)
					{
						return false;
					}
					return pouchState.Inventory.ContainsItem(item);
				}
				return false;
			}
			return true;
		}

		private static List<ItemData> EquipmentItems(Inventory inventory)
		{
			List<ItemData> list = new List<ItemData>(inventory.GetAllItems());
			PouchState pouchState = For(inventory);
			if (pouchState != null)
			{
				list.AddRange(pouchState.Inventory.GetAllItems().FindAll((ItemData i) => i.m_equipped && PouchState.CanEquip(i)));
			}
			return list;
		}
	}
	internal static class PouchInventory
	{
		[HarmonyPatch(typeof(Inventory), "GetWornItems")]
		internal static class WornItemsPatch
		{
			internal static void Postfix(Inventory __instance, List<ItemData> worn)
			{
				PouchState pouchState = For(__instance);
				if (pouchState == null)
				{
					return;
				}
				List<ItemData> list = new List<ItemData>();
				pouchState.Inventory.GetWornItems(list);
				foreach (ItemData item in list)
				{
					if (!worn.Contains(item))
					{
						worn.Add(item);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "CountItems")]
		internal static class CountPatch
		{
			internal static void Postfix(Inventory __instance, string name, int quality, bool matchWorldLevel, ref int __result)
			{
				PouchState pouchState = For(__instance);
				if (pouchState != null)
				{
					__result += pouchState.Inventory.CountItems(name, quality, matchWorldLevel);
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "CountItemsByName")]
		internal static class CountNamesPatch
		{
			internal static void Postfix(Inventory __instance, string[] names, int quality, bool matchWorldLevel, bool stacksOnly, ref int __result)
			{
				PouchState pouchState = For(__instance);
				if (pouchState != null)
				{
					__result += pouchState.Inventory.CountItemsByName(names, quality, matchWorldLevel, stacksOnly);
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "HaveItem", new Type[]
		{
			typeof(string),
			typeof(bool)
		})]
		internal static class HaveNamePatch
		{
			internal static void Postfix(Inventory __instance, string name, bool matchWorldLevel, ref bool __result)
			{
				if (!__result)
				{
					PouchState pouchState = For(__instance);
					if (pouchState != null)
					{
						__result = pouchState.Inventory.HaveItem(name, matchWorldLevel);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "HaveItem", new Type[]
		{
			typeof(ItemType),
			typeof(bool)
		})]
		internal static class HaveTypePatch
		{
			internal static void Postfix(Inventory __instance, ItemType type, bool matchWorldLevel, ref bool __result)
			{
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				if (!__result)
				{
					PouchState pouchState = For(__instance);
					if (pouchState != null)
					{
						__result = pouchState.Inventory.HaveItem(type, matchWorldLevel);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "GetItem", new Type[]
		{
			typeof(string),
			typeof(int),
			typeof(bool)
		})]
		internal static class ItemPatch
		{
			internal static void Postfix(Inventory __instance, string name, int quality, bool isPrefabName, ref ItemData? __result)
			{
				if (__result == null)
				{
					PouchState pouchState = For(__instance);
					if (pouchState != null)
					{
						__result = pouchState.Inventory.GetItem(name, quality, isPrefabName);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "GetAmmoItem")]
		internal static class AmmoPatch
		{
			internal static void Postfix(Inventory __instance, string ammoName, string matchPrefabName, ref ItemData? __result)
			{
				if (__result == null)
				{
					PouchState pouchState = For(__instance);
					if (pouchState != null)
					{
						__result = pouchState.Inventory.GetAmmoItem(ammoName, matchPrefabName);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[]
		{
			typeof(string),
			typeof(int),
			typeof(int),
			typeof(bool)
		})]
		internal static class RemoveNamePatch
		{
			internal static void Prefix(Inventory __instance, string name, int amount, int itemQuality, bool worldLevelBased, out (PouchState? Pouch, int Remaining) __state)
			{
				PouchState pouchState = For(__instance);
				__state = (Pouch: pouchState, Remaining: (pouchState != null) ? RemainingAfterMain(__instance, name, amount, itemQuality, worldLevelBased) : 0);
			}

			internal static void Postfix(string name, int itemQuality, bool worldLevelBased, (PouchState? Pouch, int Remaining) __state)
			{
				if (__state.Remaining > 0)
				{
					__state.Pouch?.ConsumeByName(name, __state.Remaining, itemQuality, worldLevelBased);
				}
			}
		}

		[HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[]
		{
			typeof(ItemData),
			typeof(int)
		})]
		internal static class RemoveAmountPatch
		{
			internal static bool Prefix(Inventory __instance, ItemData item, int amount, ref bool __result)
			{
				PouchState pouchState = For(__instance);
				if (pouchState == null || !pouchState.Inventory.ContainsItem(item))
				{
					return true;
				}
				__result = pouchState.Consume(item, amount);
				return false;
			}
		}

		[HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[] { typeof(ItemData) })]
		internal static class RemoveItemPatch
		{
			internal static bool Prefix(Inventory __instance, ItemData item, ref bool __result)
			{
				return RemoveAmountPatch.Prefix(__instance, item, item?.m_stack ?? 0, ref __result);
			}
		}

		[HarmonyPatch(typeof(Inventory), "RemoveOneItem")]
		internal static class RemoveOnePatch
		{
			internal static bool Prefix(Inventory __instance, ItemData item, ref bool __result)
			{
				return RemoveAmountPatch.Prefix(__instance, item, 1, ref __result);
			}
		}

		[HarmonyPatch(typeof(Inventory), "IsTeleportable")]
		internal static class PortalPatch
		{
			internal static void Postfix(Inventory __instance, bool allowAllItems, ref bool __result)
			{
				if (__result && Plugin.Instance.Sync.Effective.BlockPortalWithRestrictedItems)
				{
					PouchState pouchState = For(__instance);
					if (pouchState != null)
					{
						__result = pouchState.Inventory.IsTeleportable(allowAllItems);
					}
				}
			}
		}

		internal static PouchState? For(Inventory inventory)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || inventory != ((Humanoid)localPlayer).GetInventory())
			{
				return null;
			}
			PouchState current = Plugin.Current;
			if (current == null || current.Locked)
			{
				return null;
			}
			return current;
		}

		internal static bool ContainsForUse(Inventory inventory, ItemData item)
		{
			if (!inventory.ContainsItem(item))
			{
				PouchState? pouchState = For(inventory);
				if (pouchState == null)
				{
					return false;
				}
				return pouchState.Inventory.ContainsItem(item);
			}
			return true;
		}

		internal static int RemainingAfterMain(Inventory inventory, string name, int amount, int quality, bool worldLevelBased)
		{
			int num = (from i in inventory.GetAllItems()
				where i.m_shared.m_name == name && (quality < 0 || i.m_quality == quality) && (!worldLevelBased || i.m_worldLevel >= Game.m_worldLevel)
				select i).Sum((ItemData i) => i.m_stack);
			return Math.Max(0, amount - num);
		}
	}
	internal readonly struct PouchPolicy
	{
		internal const int Protocol = 3;

		internal int Slots { get; }

		internal int WeightPercent { get; }

		internal int RegularStackLimit { get; }

		internal int PortalRestrictedStackLimit { get; }

		internal bool BlockPortalWithRestrictedItems { get; }

		internal PouchPolicy(int slots, int weightPercent, int regularStackLimit = 99999, int portalRestrictedStackLimit = 1, bool blockPortalWithRestrictedItems = false)
		{
			if (slots < 1 || slots > 28 || weightPercent < 0 || weightPercent > 100)
			{
				throw new ArgumentOutOfRangeException("slots", "Invalid pouch settings.");
			}
			if (regularStackLimit < 1 || regularStackLimit > 99999 || portalRestrictedStackLimit < 1 || portalRestrictedStackLimit > 99999)
			{
				throw new ArgumentOutOfRangeException("regularStackLimit", "Invalid pouch stack limits.");
			}
			Slots = slots;
			WeightPercent = weightPercent;
			RegularStackLimit = regularStackLimit;
			PortalRestrictedStackLimit = portalRestrictedStackLimit;
			BlockPortalWithRestrictedItems = blockPortalWithRestrictedItems;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "RepairOneItem")]
	internal static class PouchRepair
	{
		internal static void Postfix()
		{
			Plugin.Current?.Store();
		}
	}
	internal static class PouchSave
	{
		internal static string Encode(int[] counts, string payload)
		{
			Validate(counts);
			return "v3|" + string.Join(",", counts) + "|" + payload;
		}

		internal static (int[] Counts, string Payload) Decode(string data)
		{
			string[] array = data.Split('|');
			if (array.Length != 3 || (array[0] != "v2" && array[0] != "v3"))
			{
				throw new FormatException("Unknown pouch save format.");
			}
			int[] array2 = array[1].Split(',').Select(int.Parse).ToArray();
			if (array[0] == "v2" && array2.Length != 4)
			{
				throw new FormatException("Invalid legacy slot count.");
			}
			Validate(array2);
			return (Counts: array2, Payload: array[2]);
		}

		private static void Validate(int[] counts)
		{
			if (counts.Length < 1 || counts.Length > 28 || counts.Any((int c) => c < 0 || c > 99999))
			{
				throw new FormatException("Invalid pouch stack counts.");
			}
		}
	}
	internal sealed class PouchState
	{
		private const string Key = "local.valheim.safepouch.inventory.v1";

		internal readonly Inventory Inventory = new Inventory("Safe Pouch", (Sprite)null, 28, 1);

		private readonly Player _player;

		private bool _equipmentRestored;

		internal int ConfiguredSlots { get; private set; }

		internal int RegularStackLimit { get; private set; }

		internal int PortalRestrictedStackLimit { get; private set; }

		internal int VisibleSlots => Math.Min(28, Math.Max(ConfiguredSlots, (from i in Inventory.GetAllItems()
			select i.m_gridPos.x + 1).DefaultIfEmpty(0).Max()));

		internal bool Locked { get; private set; }

		internal void SetSlotCount(int slots)
		{
			ConfiguredSlots = Math.Max(1, Math.Min(28, slots));
		}

		internal void SetStackLimits(int regularLimit, int restrictedLimit)
		{
			RegularStackLimit = Math.Max(1, Math.Min(99999, regularLimit));
			PortalRestrictedStackLimit = Math.Max(1, Math.Min(99999, restrictedLimit));
		}

		internal int Capacity(ItemData item)
		{
			return SlotRules.PouchCapacity(item.m_shared.m_teleportable, item.m_shared.m_maxStackSize, RegularStackLimit, PortalRestrictedStackLimit);
		}

		internal static bool CanEquip(ItemData item)
		{
			if (item.IsEquipable())
			{
				return item.m_shared.m_maxStackSize == 1;
			}
			return false;
		}

		internal static bool CanSelectEquipment(ItemData item)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Invalid comparison between Unknown and I4
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			if (!CanEquip(item) && (int)item.m_shared.m_itemType != 9)
			{
				return (int)item.m_shared.m_itemType == 23;
			}
			return true;
		}

		internal void RestoreEquipment()
		{
			if (_equipmentRestored || Locked || ((Character)_player).IsDead())
			{
				return;
			}
			_equipmentRestored = true;
			ItemData[] array = (from i in Inventory.GetAllItems()
				where i.m_equipped
				select i).ToArray();
			foreach (ItemData val in array)
			{
				val.m_equipped = false;
				if (CanSelectEquipment(val))
				{
					((Humanoid)_player).EquipItem(val, false);
				}
			}
		}

		internal void ToggleEquipment(ItemData item)
		{
			if (!Locked && !((Character)_player).IsDead() && !((Character)_player).IsTeleporting() && Inventory.ContainsItem(item) && CanSelectEquipment(item))
			{
				((Humanoid)_player).RemoveEquipAction(item);
				if (((Humanoid)_player).IsItemEquiped(item))
				{
					((Humanoid)_player).UnequipItem(item, true);
				}
				else
				{
					((Humanoid)_player).EquipItem(item, true);
				}
				Store();
			}
		}

		internal bool Consume(ItemData item, int amount)
		{
			if (Locked || amount <= 0 || !Inventory.ContainsItem(item))
			{
				return false;
			}
			amount = Math.Min(amount, item.m_stack);
			if (amount == item.m_stack)
			{
				((Humanoid)_player).UnequipItem(item, false);
			}
			bool num = Inventory.RemoveItem(item, amount);
			if (num)
			{
				Store();
			}
			return num;
		}

		internal void UseConsumable(ItemData item)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Invalid comparison between Unknown and I4
			if (!Locked && !((Character)_player).IsDead() && !((Character)_player).IsTeleporting() && Inventory.ContainsItem(item) && (int)item.m_shared.m_itemType == 2)
			{
				((Humanoid)_player).UseItem(Inventory, item, true);
				Store();
			}
		}

		internal void ConsumeByName(string name, int amount, int quality, bool matchWorldLevel)
		{
			ItemData[] array = Inventory.GetAllItems().ToArray();
			foreach (ItemData val in array)
			{
				if (amount <= 0)
				{
					break;
				}
				if (!(val.m_shared.m_name != name) && (quality < 0 || val.m_quality == quality) && (!matchWorldLevel || val.m_worldLevel >= Game.m_worldLevel))
				{
					int num = Math.Min(amount, val.m_stack);
					if (Consume(val, num))
					{
						amount -= num;
					}
				}
			}
		}

		internal PouchState(Player player, int slotCount = 4, int regularLimit = 99999, int restrictedLimit = 1)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Expected O, but got Unknown
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Expected O, but got Unknown
			ConfiguredSlots = Math.Max(1, Math.Min(28, slotCount));
			SetStackLimits(regularLimit, restrictedLimit);
			_player = player;
			if (!player.m_customData.TryGetValue("local.valheim.safepouch.inventory.v1", out var value))
			{
				return;
			}
			try
			{
				int[] array = null;
				int num;
				int num2;
				if (value.StartsWith("v2|", StringComparison.Ordinal) || value.StartsWith("v3|", StringComparison.Ordinal))
				{
					(int[], string) tuple = PouchSave.Decode(value);
					array = tuple.Item1;
					num = array.Count((int c) => c > 0);
					num2 = array.Sum();
					Inventory.Load(new ZPackage(tuple.Item2));
				}
				else
				{
					int num3 = value.IndexOf(':');
					string[] array2 = value.Substring(0, num3).Split(',');
					num = int.Parse(array2[0]);
					num2 = int.Parse(array2[1]);
					Inventory.Load(new ZPackage(value.Substring(num3 + 1)));
				}
				List<ItemData> allItems = Inventory.GetAllItems();
				int savedSlots = ((array != null) ? array.Length : 4);
				if (allItems.Count != num || allItems.Count > savedSlots || allItems.Select((ItemData i) => i.m_gridPos.x).Distinct().Count() != allItems.Count || allItems.Any((ItemData i) => i.m_gridPos.x < 0 || i.m_gridPos.x >= savedSlots || i.m_gridPos.y != 0))
				{
					throw new InvalidOperationException("Pouch data could not be restored completely. Original data retained; restore missing item mods before using this pouch.");
				}
				if (array != null)
				{
					foreach (ItemData item in allItems)
					{
						if (item.m_stack != 1 || array[item.m_gridPos.x] < 1)
						{
							throw new FormatException("Invalid pouch representative item.");
						}
						item.m_stack = array[item.m_gridPos.x];
					}
				}
				if (allItems.Sum((ItemData i) => i.m_stack) != num2 || allItems.Any((ItemData i) => i.m_stack < 1 || i.m_stack > SlotRules.PouchCapacity(i.m_shared.m_teleportable, i.m_shared.m_maxStackSize, 99999, 99999)))
				{
					throw new FormatException("Invalid pouch item counts. Original data retained.");
				}
			}
			catch (Exception error)
			{
				Locked = true;
				Plugin.Instance.Report(error);
			}
		}

		internal void Store()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Expected O, but got Unknown
			if (Locked)
			{
				return;
			}
			int[] array = new int[VisibleSlots];
			Inventory val = new Inventory("Safe Pouch save", (Sprite)null, 28, 1);
			foreach (ItemData allItem in Inventory.GetAllItems())
			{
				array[allItem.m_gridPos.x] = allItem.m_stack;
				ItemData val2 = allItem.Clone();
				val2.m_stack = 1;
				val.GetAllItems().Add(val2);
			}
			ZPackage val3 = new ZPackage();
			val.Save(val3);
			_player.m_customData["local.valheim.safepouch.inventory.v1"] = PouchSave.Encode(array, val3.GetBase64());
		}

		internal int Transfer(Inventory source, Inventory target, ItemData item, int requested, int x, int y)
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: 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)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			if (Locked || ((Character)_player).IsDead() || ((Character)_player).IsTeleporting() || !source.ContainsItem(item) || x < 0 || y < 0 || x >= target.GetWidth() || y >= target.GetHeight())
			{
				return 0;
			}
			if (target == Inventory && x >= ConfiguredSlots)
			{
				return 0;
			}
			ItemData itemAt = target.GetItemAt(x, y);
			if (itemAt == item)
			{
				return 0;
			}
			if (source == Inventory && target == Inventory && itemAt != null && (!itemAt.IsSameType(item) || itemAt.m_stack >= Capacity(item)))
			{
				Vector2i gridPos = item.m_gridPos;
				if (requested < item.m_stack || item.m_stack <= 0 || itemAt.m_stack <= 0 || gridPos.x < 0 || gridPos.x >= ConfiguredSlots || gridPos.y != 0 || Inventory.GetItemAt(gridPos.x, gridPos.y) != item || item.m_stack > Capacity(item) || itemAt.m_stack > Capacity(itemAt))
				{
					return 0;
				}
				item.m_gridPos = new Vector2i(x, y);
				itemAt.m_gridPos = gridPos;
				Plugin.InventoryChanged(Inventory);
				Store();
				return item.m_stack;
			}
			int maxStack = ((target == Inventory) ? Capacity(item) : item.m_shared.m_maxStackSize);
			int num = SlotRules.TransferAmount(restricted: false, maxStack, itemAt?.m_stack ?? 0, itemAt == null || itemAt.IsSameType(item), item.m_stack, requested);
			if (num == 0)
			{
				return 0;
			}
			if (source == Inventory && target == Inventory && itemAt == null && num == item.m_stack)
			{
				item.m_gridPos = new Vector2i(x, y);
				Plugin.InventoryChanged(Inventory);
				Store();
				return num;
			}
			((Humanoid)_player).RemoveEquipAction(item);
			if (CanEquip(item) && itemAt == null && num == item.m_stack)
			{
				source.RemoveItem(item, num);
				item.m_gridPos = new Vector2i(x, y);
				target.GetAllItems().Add(item);
				Plugin.InventoryChanged(target);
				Store();
				return num;
			}
			((Humanoid)_player).UnequipItem(item, false);
			if (target == Inventory)
			{
				if (itemAt == null)
				{
					ItemData val = item.Clone();
					val.m_stack = num;
					val.m_equipped = false;
					val.m_gridPos = new Vector2i(x, y);
					target.GetAllItems().Add(val);
				}
				else
				{
					itemAt.m_stack += num;
				}
				source.RemoveItem(item, num);
				Store();
				return num;
			}
			int stack = item.m_stack;
			target.MoveItemToThis(source, item, num, x, y);
			int result = stack - item.m_stack;
			Store();
			return result;
		}

		internal void Withdraw(ItemData item)
		{
			Inventory inventory = ((Humanoid)_player).GetInventory();
			for (int i = 0; i < 2; i++)
			{
				if (!Inventory.ContainsItem(item))
				{
					break;
				}
				for (int j = 0; j < inventory.GetHeight(); j++)
				{
					if (!Inventory.ContainsItem(item))
					{
						break;
					}
					for (int k = 0; k < inventory.GetWidth(); k++)
					{
						if (!Inventory.ContainsItem(item))
						{
							break;
						}
						if (inventory.GetItemAt(k, j) == null == (i == 1))
						{
							Transfer(Inventory, inventory, item, item.m_stack, k, j);
						}
					}
				}
			}
		}
	}
	internal sealed class PouchUi
	{
		private readonly InventoryGui _gui;

		private readonly GameObject _panel;

		private readonly Image _background;

		private Image? _inventoryBackground;

		private readonly InventoryElement[] _slots;

		private readonly TMP_Text _status;

		private PouchState? _state;

		private readonly Vector3[] _corners = (Vector3[])(object)new Vector3[4];

		private readonly List<Button> _inventoryButtons = new List<Button>();

		private static readonly string[] BackgroundPaths = new string[4] { "Bkg", "Background", "Darken", "selected_frame/selected (1)" };

		internal int SlotCount => _slots.Length;

		internal bool Alive
		{
			get
			{
				if (Object.op_Implicit((Object)(object)_panel))
				{
					return Object.op_Implicit((Object)(object)_gui);
				}
				return false;
			}
		}

		internal PouchUi(InventoryGui gui, int slotCount)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Expected O, but got Unknown
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0308: Unknown result type (might be due to invalid IL or missing references)
			//IL_030f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0316: Unknown result type (might be due to invalid IL or missing references)
			//IL_0321: Unknown result type (might be due to invalid IL or missing references)
			//IL_0342: Unknown result type (might be due to invalid IL or missing references)
			_slots = (InventoryElement[])(object)new InventoryElement[slotCount];
			_gui = gui;
			float elementSpace = gui.m_playerGrid.m_elementSpace;
			int num = Math.Min(7, slotCount);
			int num2 = (slotCount + 7 - 1) / 7;
			float num3 = ((num == 1) ? 60 : 38);
			_panel = new GameObject("SafePouch", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			});
			RectTransform val = (RectTransform)_panel.transform;
			((Transform)val).SetParent((Transform)(object)gui.m_player, false);
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0f, 0f);
			val.anchorMax = val2;
			val.anchorMin = val2;
			val.pivot = new Vector2(0f, 1f);
			val.anchoredPosition = new Vector2(0f, -8f);
			val.sizeDelta = new Vector2(elementSpace * (float)num + 24f, elementSpace * (float)num2 + num3 + 40f);
			_background = _panel.GetComponent<Image>();
			Image val3 = ((Component)gui.m_player).GetComponent<Image>();
			if (!Object.op_Implicit((Object)(object)val3) || !Object.op_Implicit((Object)(object)val3.sprite))
			{
				val3 = (from Transform t in (IEnumerable)gui.m_player
					where string.Equals(((Object)t).name, "Bkg", StringComparison.OrdinalIgnoreCase) || string.Equals(((Object)t).name, "Background", StringComparison.OrdinalIgnoreCase)
					select ((Component)t).GetComponent<Image>()).FirstOrDefault((Func<Image, bool>)((Image i) => Object.op_Implicit((Object)(object)i) && Object.op_Implicit((Object)(object)i.sprite)));
			}
			_inventoryBackground = val3;
			((Graphic)_background).color = new Color(0.16f, 0.14f, 0.15f, 0.97f);
			((Graphic)_background).raycastTarget = true;
			SyncBackgroundStyle();
			MakeText((num == 1) ? "Safe\nPouch" : "Safe Pouch", new Vector2(12f, -7f), 23f, new Color(1f, 0.77f, 0.38f), gui.m_containerName, num3 - 9f);
			_status = MakeText("Kept on death", new Vector2(12f, (0f - elementSpace) * (float)num2 - num3 - 5f), 16f, new Color(0.85f, 0.82f, 0.74f), gui.m_recipeDecription);
			for (int num4 = 0; num4 < slotCount; num4++)
			{
				int slotIndex = num4;
				GameObject val4 = Object.Instantiate<GameObject>(gui.m_playerGrid.m_elementPrefab, (Transform)(object)val);
				((Object)val4).name = "SafePouchSlot" + num4;
				RectTransform val5 = (RectTransform)val4.transform;
				((Vector2)(ref val2))..ctor(0f, 1f);
				val5.anchorMax = val2;
				val5.anchorMin = val2;
				val5.pivot = new Vector2(0f, 1f);
				val5.anchoredPosition = new Vector2(12f + elementSpace * (float)(num4 % 7), 0f - num3 - elementSpace * (float)(num4 / 7));
				InventoryElement component = val4.GetComponent<InventoryElement>();
				component.Initialize(num4, 0);
				_slots[num4] = component;
				UIInputHandler componentInChildren = val4.GetComponentInChildren<UIInputHandler>();
				componentInChildren.m_onLeftDown = delegate
				{
					Click(slotIndex);
				};
				componentInChildren.m_onRightDown = delegate
				{
					Withdraw(slotIndex);
				};
				UIDragHandler[] componentsInChildren = val4.GetComponentsInChildren<UIDragHandler>(true);
				for (int num5 = 0; num5 < componentsInChildren.Length; num5++)
				{
					((Behaviour)componentsInChildren[num5]).enabled = false;
				}
				Transform obj = val4.transform.Find("binding");
				TMP_Text val6 = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null);
				if (Object.op_Implicit((Object)(object)val6))
				{
					((Behaviour)val6).enabled = false;
				}
			}
		}

		private TMP_Text MakeText(string text, Vector2 position, float size, Color color, TMP_Text style, float height = 32f)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: 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_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: 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)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: 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)
			GameObject val = new GameObject(text, new Type[2]
			{
				typeof(RectTransform),
				typeof(TextMeshProUGUI)
			});
			RectTransform val2 = (RectTransform)val.transform;
			((Transform)val2).SetParent(_panel.transform, false);
			Vector2 val3 = default(Vector2);
			((Vector2)(ref val3))..ctor(0f, 1f);
			val2.anchorMax = val3;
			val2.anchorMin = val3;
			val2.pivot = new Vector2(0f, 1f);
			val2.anchoredPosition = position;
			val2.sizeDelta = new Vector2(((RectTransform)_panel.transform).sizeDelta.x - 24f, height);
			TextMeshProUGUI component = val.GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).font = style.font;
			((TMP_Text)component).fontSharedMaterial = style.fontSharedMaterial;
			((TMP_Text)component).fontSize = size;
			((TMP_Text)component).enableAutoSizing = true;
			((TMP_Text)component).fontSizeMin = 12f;
			((TMP_Text)component).fontSizeMax = size;
			((TMP_Text)component).enableWordWrapping = true;
			((Graphic)component).color = color;
			((TMP_Text)component).text = text;
			((Graphic)component).raycastTarget = false;
			return (TMP_Text)(object)component;
		}

		internal void Refresh(PouchState state)
		{
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0256: Unknown result type (might be due to invalid IL or missing references)
			//IL_036b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0371: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e3: Invalid comparison between Unknown and I4
			_state = state;
			_panel.SetActive(InventoryGui.IsVisible() && !((Character)Player.m_localPlayer).IsDead() && !_gui.IsContainerOpen());
			if (!_panel.activeInHierarchy)
			{
				return;
			}
			SyncBackgroundStyle();
			UpdatePosition();
			_status.text = (state.Locked ? "Pouch locked — check log" : ((state.VisibleSlots > state.ConfiguredSlots) ? "Extra slots: withdraw only" : "Kept on death"));
			for (int i = 0; i < _slots.Length; i++)
			{
				InventoryElement val = _slots[i];
				ItemData itemAt = state.Inventory.GetItemAt(i, 0);
				((Behaviour)val.m_icon).enabled = itemAt != null;
				if (itemAt != null)
				{
					val.m_icon.sprite = itemAt.GetIcon();
				}
				((Behaviour)val.m_amount).enabled = itemAt != null && itemAt.m_stack > 1;
				val.m_amount.text = itemAt?.m_stack.ToString() ?? "";
				((Behaviour)val.m_quality).enabled = itemAt != null && itemAt.m_shared.m_maxQuality > 1;
				val.m_quality.text = itemAt?.m_quality.ToString() ?? "";
				((Behaviour)val.m_equiped).enabled = itemAt != null && ((Humanoid)Player.m_localPlayer).IsItemEquiped(itemAt);
				((Behaviour)val.m_queued).enabled = false;
				((Behaviour)val.m_food).enabled = false;
				((Behaviour)val.m_noteleport).enabled = itemAt != null && !itemAt.m_shared.m_teleportable && Plugin.Instance.Sync.Effective.BlockPortalWithRestrictedItems;
				val.m_selected.SetActive(itemAt != null && Drag.Item(_gui) == itemAt);
				bool flag = itemAt != null && itemAt.m_shared.m_useDurability && itemAt.m_durability < itemAt.GetMaxDurability();
				((Component)val.m_durability).gameObject.SetActive(flag);
				if (flag)
				{
					val.m_durability.SetValue((itemAt.m_durability <= 0f) ? 1f : itemAt.GetDurabilityPercentage());
					if (itemAt.m_durability <= 0f)
					{
						val.m_durability.SetColor((Mathf.Sin(Time.time * 10f) > 0f) ? Color.red : Color.clear);
					}
					else
					{
						val.m_durability.ResetColor();
					}
				}
				val.m_tooltip.Set(itemAt?.m_shared.m_name ?? "Safe Pouch", (i >= state.ConfiguredSlots) ? "Withdraw this item to finish reducing the pouch size." : ((itemAt == null) ? "Items here survive death." : (itemAt.GetTooltip(-1) + "\n<color=orange>Kept on death</color>" + (PouchState.CanSelectEquipment(itemAt) ? "\nShift + right-click: equip / unequip" : "") + (((int)itemAt.m_shared.m_itemType == 2) ? "\nShift + right-click: use" : "") + ((itemAt.m_stack > state.Capacity(itemAt)) ? "\nAbove the current limit: withdraw to reduce this stack." : "") + ((!itemAt.m_shared.m_teleportable && !Plugin.Instance.Sync.Effective.BlockPortalWithRestrictedItems) ? "\nCan travel through portals here." : ""))), _gui.m_playerGrid.m_tooltipAnchor, default(Vector2));
			}
		}

		private void UpdatePosition()
		{
			//IL_000d: 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_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			RectTransform player = _gui.m_player;
			Rect rect = player.rect;
			float bottom = ((Rect)(ref rect)).yMin;
			string[] backgroundPaths = BackgroundPaths;
			foreach (string text in backgroundPaths)
			{
				Transform obj = ((Transform)player).Find(text);
				RectTransform val = (RectTransform)(object)((obj is RectTransform) ? obj : null);
				if (Object.op_Implicit((Object)(object)val) && ((Component)val).gameObject.activeInHierarchy)
				{
					IncludeBottom(val, ref bottom);
				}
			}
			((Component)player).GetComponentsInChildren<Button>(false, _inventoryButtons);
			foreach (Button inventoryButton in _inventoryButtons)
			{
				if (Object.op_Implicit((Object)(object)inventoryButton) && ((Behaviour)inventoryButton).isActiveAndEnabled && !((Component)inventoryButton).transform.IsChildOf(_panel.transform))
				{
					Transform transform = ((Component)inventoryButton).transform;
					RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null);
					if (Object.op_Implicit((Object)(object)val2))
					{
						IncludeBottom(val2, ref bottom);
					}
				}
			}
			RectTransform val3 = (RectTransform)_panel.transform;
			float num = bottom;
			rect = player.rect;
			val3.anchoredPosition = new Vector2(0f, num - ((Rect)(ref rect)).yMin - Plugin.Instance.PouchGap.Value - Plugin.Instance.VerticalOffset.Value);
		}

		private void SyncBackgroundStyle()
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: 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_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)_inventoryBackground))
			{
				Image inventoryBackground = _inventoryBackground;
				if ((Object)(object)((Graphic)_background).material != (Object)(object)((Graphic)inventoryBackground).material)
				{
					((Graphic)_background).material = ((Graphic)inventoryBackground).material;
				}
				if ((Object)(object)_background.sprite != (Object)(object)inventoryBackground.sprite)
				{
					_background.sprite = inventoryBackground.sprite;
				}
				if ((Object)(object)_background.overrideSprite != (Object)(object)inventoryBackground.overrideSprite)
				{
					_background.overrideSprite = inventoryBackground.overrideSprite;
				}
				if (((Graphic)_background).color != ((Graphic)inventoryBackground).color)
				{
					((Graphic)_background).color = ((Graphic)inventoryBackground).color;
				}
				if (_background.type != inventoryBackground.type)
				{
					_background.type = inventoryBackground.type;
				}
				if (_background.pixelsPerUnitMultiplier != inventoryBackground.pixelsPerUnitMultiplier)
				{
					_background.pixelsPerUnitMultiplier = inventoryBackground.pixelsPerUnitMultiplier;
				}
				if (_background.fillCenter != inventoryBackground.fillCenter)
				{
					_background.fillCenter = inventoryBackground.fillCenter;
				}
				if (_background.preserveAspect != inventoryBackground.preserveAspect)
				{
					_background.preserveAspect = inventoryBackground.preserveAspect;
				}
			}
		}

		private void IncludeBottom(RectTransform edge, ref float bottom)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: 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_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			edge.GetWorldCorners(_corners);
			float num = float.PositiveInfinity;
			float num2 = float.NegativeInfinity;
			float num3 = float.PositiveInfinity;
			Vector3[] corners = _corners;
			foreach (Vector3 val in corners)
			{
				Vector3 val2 = ((Transform)_gui.m_player).InverseTransformPoint(val);
				num = Mathf.Min(num, val2.x);
				num2 = Mathf.Max(num2, val2.x);
				num3 = Mathf.Min(num3, val2.y);
			}
			float num4 = num2;
			Rect rect = _gui.m_player.rect;
			if (num4 > ((Rect)(ref rect)).xMin)
			{
				float num5 = num;
				rect = _gui.m_player.rect;
				if (num5 < ((Rect)(ref rect)).xMax)
				{
					bottom = Mathf.Min(bottom, num3);
				}
			}
		}

		private void Click(int index)
		{
			if (_state == null || _state.Locked || ((Character)Player.m_localPlayer).IsDead() || ((Character)Player.m_localPlayer).IsTeleporting())
			{
				return;
			}
			ItemData val = Drag.Item(_gui);
			Inventory val2 = Drag.Inventory(_gui);
			if (val != null && val2 != null)
			{
				if (index >= _state.ConfiguredSlots)
				{
					return;
				}
				if (val2 != ((Humanoid)Player.m_localPlayer).GetInventory() && val2 != _state.Inventory)
				{
					((Character)Player.m_localPlayer).Message((MessageType)2, "Move the item into your inventory first.", 0, (Sprite)null, false);
					return;
				}
				int num = Math.Min(val.m_stack, Drag.Amount(_gui));
				if (!val.m_shared.m_teleportable && val2 != _state.Inventory)
				{
					for (int i = 0; i < _state.ConfiguredSlots; i++)
					{
						if (num <= 0)
						{
							break;
						}
						num -= _state.Transfer(val2, _state.Inventory, val, num, (index + i) % _state.ConfiguredSlots, 0);
					}
				}
				else
				{
					_state.Transfer(val2, _state.Inventory, val, num, index, 0);
				}
				Drag.Clear(_gui);
			}
			else
			{
				ItemData itemAt = _state.Inventory.GetItemAt(index, 0);
				if (itemAt != null)
				{
					Drag.Set(_gui, itemAt, _state.Inventory, itemAt.m_stack);
				}
			}
		}

		private void Withdraw(int index)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Invalid comparison between Unknown and I4
			if (_state == null || _state.Locked)
			{
				return;
			}
			Drag.Clear(_gui);
			ItemData itemAt = _state.Inventory.GetItemAt(index, 0);
			if (itemAt == null)
			{
				return;
			}
			if (Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303))
			{
				if ((int)itemAt.m_shared.m_itemType == 2)
				{
					_state.UseConsumable(itemAt);
				}
				else
				{
					_state.ToggleEquipment(itemAt);
				}
			}
			else
			{
				_state.Withdraw(itemAt);
			}
		}

		internal void Destroy()
		{
			Object.Destroy((Object)(object)_panel);
		}
	}
	internal static class SlotRules
	{
		internal const int Slots = 4;

		internal const int MaxSlots = 28;

		internal const int Columns = 7;

		internal const int PouchStackLimit = 99999;

		internal static int PouchCapacity(bool teleportable, int normalMax, int regularLimit = 99999, int restrictedLimit = 1)
		{
			if (normalMax > 1)
			{
				return Math.Max(1, Math.Min(99999, teleportable ? regularLimit : restrictedLimit));
			}
			return 1;
		}

		internal static int TransferAmount(bool restricted, int maxStack, int existing, bool compatible, int available, int requested)
		{
			if (!compatible || available <= 0 || requested <= 0 || existing < 0)
			{
				return 0;
			}
			int num = (restricted ? 1 : Math.Max(1, maxStack));
			return Math.Max(0, Math.Min(Math.Min(available, requested), num - existing));
		}
	}
}