Decompiled source of CraftFromStorage v1.0.0

plugins/CraftFromStorage.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("CraftFromStorage")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Craft, build and smelt with materials from nearby chests. Built on Multi User Chest for multiplayer; works with Quick Stack Store Sort Trash Restock and Epic Loot.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+8c6e7c1275a24001b379f08cded2dec3467d65bc")]
[assembly: AssemblyProduct("CraftFromStorage")]
[assembly: AssemblyTitle("CraftFromStorage")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CraftFromStorage
{
	internal static class ChestAccess
	{
		public sealed class Pull
		{
			public Container Chest;

			public string Name;

			public int Amount;

			public Vector2i Slot;

			public int StartCount;

			public float Deadline;

			public bool Finished;
		}

		public sealed class PullPlan
		{
			public readonly List<(Container Chest, ItemData Item, int Amount, Vector2i Slot)> Steps = new List<(Container, ItemData, int, Vector2i)>();

			public int Missing;

			public int SlotsShort;

			public bool Ok
			{
				get
				{
					if (Missing == 0)
					{
						return SlotsShort == 0;
					}
					return false;
				}
			}

			public int Total => Steps.Sum(((Container Chest, ItemData Item, int Amount, Vector2i Slot) s) => s.Amount);
		}

		public sealed class SlotReservation
		{
			private readonly HashSet<Vector2i> m_reserved = new HashSet<Vector2i>(s_pending.Select((Pull p) => p.Slot));

			public bool TryReserve(Inventory inventory, ItemData item, int amount, out Vector2i slot)
			{
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_010b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_005d: 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_00b9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
				//IL_00df: Unknown result type (might be due to invalid IL or missing references)
				foreach (ItemData item2 in inventory.m_inventory)
				{
					if (!m_reserved.Contains(item2.m_gridPos) && item2.IsSameType(item) && item2.m_quality == item.m_quality && item2.m_shared.m_maxStackSize - item2.m_stack >= amount)
					{
						slot = item2.m_gridPos;
						m_reserved.Add(slot);
						return true;
					}
				}
				Vector2i val = default(Vector2i);
				for (int i = 1; i <= inventory.GetHeight(); i++)
				{
					int num = i % inventory.GetHeight();
					for (int j = 0; j < inventory.GetWidth(); j++)
					{
						((Vector2i)(ref val))..ctor(j, num);
						if (!m_reserved.Contains(val) && inventory.GetItemAt(j, num) == null)
						{
							slot = val;
							m_reserved.Add(slot);
							return true;
						}
					}
				}
				slot = default(Vector2i);
				return false;
			}
		}

		private const string BorrowRpc = "s992.CraftFromStorage.Borrow";

		private const string BorrowReplyRpc = "s992.CraftFromStorage.BorrowReply";

		private const float BorrowTimeout = 5f;

		private const float BorrowCooldown = 15f;

		private const float BorrowForInterval = 3f;

		private const int MaxBorrowsPerCall = 8;

		private const float PullTimeout = 5f;

		private static readonly Dictionary<ZDOID, float> s_borrowPending = new Dictionary<ZDOID, float>();

		private static readonly Dictionary<ZDOID, float> s_awaitingOwnership = new Dictionary<ZDOID, float>();

		private static readonly Dictionary<ZDOID, float> s_borrowCooldown = new Dictionary<ZDOID, float>();

		private static readonly Dictionary<string, float> s_nextBorrowFor = new Dictionary<string, float>();

		private static readonly Dictionary<ZDOID, float> s_borrowedAt = new Dictionary<ZDOID, float>();

		private const float KeepBorrowed = 30f;

		private const int MaxBorrowsInFlight = 3;

		private const float MinSecondsBetweenBorrows = 0.3f;

		private static float s_nextBorrowAllowed;

		private static readonly List<Pull> s_pending = new List<Pull>();

		private static readonly List<ZDOID> s_expired = new List<ZDOID>();

		public static int BorrowsSent { get; private set; }

		public static int BorrowsGranted { get; private set; }

		public static int HandedAway { get; private set; }

		public static int PendingPulls => s_pending.Count;

		public static bool PrepareOwned(Container chest)
		{
			ZNetView nview = chest.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
			{
				return false;
			}
			if (!nview.IsOwner())
			{
				if (nview.GetZDO().GetOwner() != 0L || !StorageNetwork.IsSinglePlayer())
				{
					return false;
				}
				nview.ClaimOwnership();
			}
			if (nview.GetZDO().DataRevision != chest.m_lastRevision && !chest.m_inUse)
			{
				chest.Load();
			}
			return true;
		}

		public static int TakeFromOwned(string name, int amount, int quality, bool worldLevelBased, string reason)
		{
			int num = amount;
			foreach (Container item in StorageNetwork.Usable)
			{
				if (num <= 0)
				{
					break;
				}
				if (!StorageNetwork.IsOwnedForUse(item) || !PrepareOwned(item))
				{
					continue;
				}
				Inventory inventory = item.GetInventory();
				foreach (ItemData item2 in inventory.m_inventory.ToList())
				{
					if (num <= 0)
					{
						break;
					}
					if (StorageNetwork.Matches(item2, name, quality, worldLevelBased))
					{
						int num2 = Mathf.Min(item2.m_stack, num);
						if (inventory.RemoveItem(item2, num2))
						{
							num -= num2;
							StorageLog.Action($"took {num2} {name} from {Describe(item)} for {reason}");
						}
					}
				}
			}
			return amount - num;
		}

		public static void RegisterRpcs()
		{
			ZRoutedRpc.instance.Register<ZDOID>("s992.CraftFromStorage.Borrow", (Action<long, ZDOID>)RPC_Borrow);
			ZRoutedRpc.instance.Register<ZDOID, bool>("s992.CraftFromStorage.BorrowReply", (Action<long, ZDOID, bool>)RPC_BorrowReply);
		}

		public static void BorrowFor(string key, Func<ItemData, bool> match)
		{
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			if (!Settings.Enabled.Value || !Settings.Borrow.Value || ZRoutedRpc.instance == null || (s_nextBorrowFor.TryGetValue(key, out var value) && Time.time < value))
			{
				return;
			}
			s_nextBorrowFor[key] = Time.time + 3f;
			int num = 0;
			foreach (Container candidate in StorageNetwork.Candidates)
			{
				if (num >= 8 || !BudgetAllows())
				{
					break;
				}
				ZNetView nview = candidate.m_nview;
				if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid() || nview.IsOwner())
				{
					continue;
				}
				ZDO zDO = nview.GetZDO();
				long owner = zDO.GetOwner();
				if (owner != 0L && zDO.GetInt(ZDOVars.s_inUse, 0) != 1)
				{
					ZDOID uid = zDO.m_uid;
					if (!s_borrowPending.ContainsKey(uid) && !s_awaitingOwnership.ContainsKey(uid) && (!s_borrowCooldown.TryGetValue(uid, out var value2) || !(Time.time < value2)) && candidate.GetInventory().m_inventory.Any((ItemData item) => StorageNetwork.IsUsableItem(item) && match(item)))
					{
						s_borrowPending[uid] = Time.time + 5f;
						s_nextBorrowAllowed = Time.time + 0.3f;
						ZRoutedRpc.instance.InvokeRoutedRPC(owner, "s992.CraftFromStorage.Borrow", new object[1] { uid });
						BorrowsSent++;
						num++;
					}
				}
			}
		}

		private static bool BudgetAllows()
		{
			if (Time.time >= s_nextBorrowAllowed)
			{
				return s_borrowPending.Count + s_awaitingOwnership.Count < 3;
			}
			return false;
		}

		public static void BorrowFor(string itemName)
		{
			BorrowFor(itemName, (ItemData item) => item.m_shared.m_name == itemName);
		}

		private static void RPC_Borrow(long sender, ZDOID id)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: 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)
			bool flag = false;
			ZDO zDO = ZDOMan.instance.GetZDO(id);
			ZNetView val = ((zDO != null) ? ZNetScene.instance.FindInstance(zDO) : null);
			Container val2 = (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponentInChildren<Container>() : null);
			float value;
			bool flag2 = s_borrowedAt.TryGetValue(id, out value) && Time.time - value < 30f;
			if (Object.op_Implicit((Object)(object)val2) && val.IsOwner() && sender != ZDOMan.GetSessionID() && !flag2 && !val2.IsInUse() && (!Object.op_Implicit((Object)(object)val2.m_wagon) || !val2.m_wagon.InUse()) && (!Object.op_Implicit((Object)(object)InventoryGui.instance) || !((Object)(object)InventoryGui.instance.m_currentContainer == (Object)(object)val2)))
			{
				ZDOMan.instance.ForceSendZDO(sender, zDO.m_uid);
				zDO.SetOwner(sender);
				flag = true;
				HandedAway++;
				StorageLog.Action($"handed {Describe(val2)} to player {sender}");
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(sender, "s992.CraftFromStorage.BorrowReply", new object[2] { id, flag });
		}

		private static void RPC_BorrowReply(long sender, ZDOID id, bool granted)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: 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)
			s_borrowPending.Remove(id);
			s_borrowCooldown[id] = Time.time + 15f;
			if (granted)
			{
				s_awaitingOwnership[id] = Time.time + 5f;
				s_borrowedAt[id] = Time.time;
				BorrowsGranted++;
			}
		}

		public static PullPlan PlanPulls(string name, int quality, int amount, SlotReservation slots)
		{
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			PullPlan pullPlan = new PullPlan();
			Player localPlayer = Player.m_localPlayer;
			int num = amount;
			if (!Object.op_Implicit((Object)(object)localPlayer) || amount <= 0 || !MucHealth.CanUseNonOwnedChests)
			{
				pullPlan.Missing = Mathf.Max(0, amount);
				return pullPlan;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			foreach (Container item in StorageNetwork.Usable)
			{
				if (num <= 0)
				{
					break;
				}
				if (StorageNetwork.IsOwnedForUse(item))
				{
					continue;
				}
				foreach (ItemData item2 in item.GetInventory().m_inventory)
				{
					if (num <= 0)
					{
						break;
					}
					if (StorageNetwork.Matches(item2, name, quality, matchWorldLevel: true))
					{
						int num2 = Mathf.Min(new int[3]
						{
							item2.m_stack,
							num,
							item2.m_shared.m_maxStackSize
						});
						num -= num2;
						if (slots.TryReserve(inventory, item2, num2, out var slot))
						{
							pullPlan.Steps.Add((item, item2, num2, slot));
						}
						else
						{
							pullPlan.SlotsShort++;
						}
					}
				}
			}
			pullPlan.Missing = num;
			return pullPlan;
		}

		public static List<Pull> Start(PullPlan plan, string reason)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			List<Pull> list = new List<Pull>();
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer))
			{
				return list;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			foreach (var (val, val2, num, val3) in plan.Steps)
			{
				if (!Object.op_Implicit((Object)(object)val) || !val.GetInventory().ContainsItem(val2))
				{
					continue;
				}
				string name = val2.m_shared.m_name;
				Pull pull = new Pull
				{
					Chest = val,
					Name = name,
					Amount = num,
					Slot = val3,
					StartCount = inventory.CountItems(name, -1, false),
					Deadline = Time.time + 5f
				};
				if (StorageNetwork.IsOwnedForUse(val))
				{
					if (PrepareOwned(val) && val.GetInventory().ContainsItem(val2))
					{
						inventory.MoveItemToThis(val.GetInventory(), val2, num, val3.x, val3.y);
						pull.Finished = true;
						StorageLog.Action($"moved {num} {name} from {Describe(val)} for {reason}");
						list.Add(pull);
					}
				}
				else
				{
					bool flag = inventory.MoveItemToThis(val.GetInventory(), val2, num, val3.x, val3.y);
					if (MucInternals.Available ? (!MucInternals.IsSlotBlocked(inventory, val3)) : (!flag))
					{
						StorageLog.Warning($"Couldn't fetch {num} {name} from {Describe(val)}: MUC didn't send the request");
						continue;
					}
					s_pending.Add(pull);
					list.Add(pull);
					StorageLog.Action($"fetching {num} {name} from {Describe(val)} for {reason}");
				}
			}
			return list;
		}

		public static void Tick()
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: 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_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0232: Unknown result type (might be due to invalid IL or missing references)
			float time = Time.time;
			ExpireAll(s_borrowPending, time, delegate(ZDOID id)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				StorageNetwork.MarkUnresponsive(FindChest(id));
			});
			ExpireAll(s_borrowCooldown, time, null);
			ExpireAll(s_borrowedAt, time - 30f, null);
			if (s_awaitingOwnership.Count > 0)
			{
				foreach (ZDOID item in s_awaitingOwnership.Keys.ToList())
				{
					Container val = FindChest(item);
					if (Object.op_Implicit((Object)(object)val) && val.m_nview.IsOwner())
					{
						PrepareOwned(val);
						s_awaitingOwnership.Remove(item);
						StorageNetwork.Invalidate();
						StorageLog.Action("borrowed " + Describe(val));
					}
					else if (time > s_awaitingOwnership[item])
					{
						s_awaitingOwnership.Remove(item);
					}
				}
			}
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || s_pending.Count == 0)
			{
				return;
			}
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			foreach (Pull item2 in s_pending)
			{
				if (MucInternals.Available ? (!MucInternals.IsSlotBlocked(inventory, item2.Slot)) : (inventory.CountItems(item2.Name, -1, false) >= item2.StartCount + item2.Amount))
				{
					item2.Finished = true;
					if (inventory.CountItems(item2.Name, -1, false) <= item2.StartCount)
					{
						if (Object.op_Implicit((Object)(object)item2.Chest))
						{
							StorageNetwork.MarkUnresponsive(item2.Chest);
						}
						StorageLog.Action($"fetch of {item2.Amount} {item2.Name} from {Describe(item2.Chest)} came back empty");
					}
					else
					{
						StorageLog.Action($"fetch of {item2.Amount} {item2.Name} from {Describe(item2.Chest)} answered");
					}
				}
				else if (time > item2.Deadline)
				{
					MucInternals.ReleaseSlot(inventory, item2.Slot);
					item2.Finished = true;
					if (Object.op_Implicit((Object)(object)item2.Chest))
					{
						StorageNetwork.MarkUnresponsive(item2.Chest);
					}
					StorageLog.Warning($"Fetch of {item2.Amount} {item2.Name} from {Describe(item2.Chest)} got no reply in {5f:0} s");
					StorageLog.Action($"fetch of {item2.Amount} {item2.Name} from {Describe(item2.Chest)} timed out");
				}
			}
			if (s_pending.RemoveAll((Pull p) => p.Finished) > 0)
			{
				StorageNetwork.Invalidate();
			}
		}

		private static void ExpireAll(Dictionary<ZDOID, float> deadlines, float now, Action<ZDOID> onExpired)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			if (deadlines.Count == 0)
			{
				return;
			}
			s_expired.Clear();
			foreach (KeyValuePair<ZDOID, float> deadline in deadlines)
			{
				if (now > deadline.Value)
				{
					s_expired.Add(deadline.Key);
				}
			}
			foreach (ZDOID item in s_expired)
			{
				deadlines.Remove(item);
				onExpired?.Invoke(item);
			}
		}

		private static Container FindChest(ZDOID id)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			ZDOMan instance = ZDOMan.instance;
			ZDO val = ((instance != null) ? instance.GetZDO(id) : null);
			ZNetView val2 = ((val != null) ? ZNetScene.instance.FindInstance(val) : null);
			if (!Object.op_Implicit((Object)(object)val2))
			{
				return null;
			}
			return ((Component)val2).GetComponentInChildren<Container>();
		}

		public static string Describe(Container chest)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: 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)
			if (!Object.op_Implicit((Object)(object)chest))
			{
				return "a chest that's gone";
			}
			Vector3 position = ((Component)chest).transform.position;
			string text = (Object.op_Implicit((Object)(object)chest.m_nview) ? StorageNetwork.PrefabName(((Component)chest.m_nview).gameObject) : ((Object)chest).name);
			return $"{text} at ({position.x:0}, {position.y:0}, {position.z:0})";
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class ContainerAwakePatch
	{
		private static void Postfix(Container __instance)
		{
			StorageNetwork.Register(__instance);
		}
	}
	[HarmonyPatch(typeof(Inventory), "Changed")]
	internal static class InventoryChangedPatch
	{
		private static void Postfix(Inventory __instance)
		{
			StorageNetwork.OnInventoryChanged(__instance);
		}
	}
	[HarmonyPatch(typeof(Game), "Start")]
	internal static class GameStartPatch
	{
		private static void Postfix()
		{
			ChestAccess.RegisterRpcs();
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	internal static class TerminalPatch
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__0_0;

			public static ConsoleOptionsFetcher <>9__0_1;

			internal void <Postfix>b__0_0(ConsoleEventArgs args)
			{
				if (((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status") == "audit")
				{
					Audit(args.Context);
					return;
				}
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString(Status.Line());
				}
				Terminal context2 = args.Context;
				if (context2 != null)
				{
					context2.AddString($"Borrowing this session: asked {ChestAccess.BorrowsSent}, granted {ChestAccess.BorrowsGranted}, " + $"handed to others {ChestAccess.HandedAway}");
				}
			}

			internal List<string> <Postfix>b__0_1()
			{
				return new List<string> { "status", "audit" };
			}
		}

		private static void Postfix()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_0051: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			object obj = <>c.<>9__0_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					if (((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status") == "audit")
					{
						Audit(args.Context);
					}
					else
					{
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString(Status.Line());
						}
						Terminal context2 = args.Context;
						if (context2 != null)
						{
							context2.AddString($"Borrowing this session: asked {ChestAccess.BorrowsSent}, granted {ChestAccess.BorrowsGranted}, " + $"handed to others {ChestAccess.HandedAway}");
						}
					}
				};
				<>c.<>9__0_0 = val;
				obj = (object)val;
			}
			object obj2 = <>c.<>9__0_1;
			if (obj2 == null)
			{
				ConsoleOptionsFetcher val2 = () => new List<string> { "status", "audit" };
				<>c.<>9__0_1 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("storage", "[status|audit] - nearby storage: what's used, and item totals", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false);
		}

		private static void Audit(Terminal terminal)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer))
			{
				if (terminal != null)
				{
					terminal.AddString("Storage audit needs a player in the world");
				}
				return;
			}
			SortedDictionary<string, int> totals = new SortedDictionary<string, int>();
			IReadOnlyList<Container> candidates = StorageNetwork.Candidates;
			foreach (Container item in candidates)
			{
				Add(item.GetInventory());
			}
			Add(((Humanoid)localPlayer).GetInventory());
			int num = totals.Values.Sum();
			string text = $"Storage audit: {candidates.Count} chests in reach + your inventory: {num} items, {totals.Count} kinds";
			if (terminal != null)
			{
				terminal.AddString(text + " (full list in BepInEx/CraftFromStorage.log)");
			}
			StorageLog.Write("AUDIT " + text);
			foreach (KeyValuePair<string, int> item2 in totals)
			{
				StorageLog.Write($"AUDIT   {Localization.instance.Localize(item2.Key)} ({item2.Key}): {item2.Value}");
			}
			void Add(Inventory inventory)
			{
				foreach (ItemData item3 in inventory.m_inventory)
				{
					string name = item3.m_shared.m_name;
					totals[name] = (totals.TryGetValue(name, out var value) ? value : 0) + item3.m_stack;
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "HaveRequirementItems")]
	internal static class HaveRequirementItemsPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return Hooks.ReplaceCalls(instructions, Hooks.CountItems, Hooks.Self("CountForCrafting"), 1, "Player.HaveRequirementItems");
		}
	}
	[HarmonyPatch(typeof(Player), "HaveRequirements", new Type[]
	{
		typeof(Piece),
		typeof(RequirementMode)
	})]
	internal static class HaveRequirementsPiecePatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return Hooks.ReplaceCalls(Hooks.ReplaceCalls(instructions, Hooks.CountItems, Hooks.Self("CountForBuilding"), 1, "Player.HaveRequirements(Piece)"), Hooks.HaveItem, Hooks.Self("HaveForBuilding"), 1, "Player.HaveRequirements(Piece)");
		}
	}
	[HarmonyPatch(typeof(Player), "GetFirstRequiredItem")]
	internal static class GetFirstRequiredItemPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return Hooks.ReplaceCalls(Hooks.ReplaceCalls(instructions, Hooks.CountItems, Hooks.Self("CountForCrafting"), 1, "Player.GetFirstRequiredItem"), Hooks.GetItem, Hooks.Self("GetItemForCrafting"), 1, "Player.GetFirstRequiredItem");
		}
	}
	[HarmonyPatch(typeof(Player), "ConsumeResources")]
	internal static class ConsumeResourcesPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return Hooks.ReplaceCalls(instructions, Hooks.RemoveItemByName, Hooks.Self("RemoveWithStorage"), 1, "Player.ConsumeResources");
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "SetupRequirement")]
	internal static class SetupRequirementPatch
	{
		private static void Postfix(bool __result, Transform elementRoot, Requirement req, Player player, bool craft, int quality, int craftMultiplier)
		{
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			if (__result && !((Object)(object)req?.m_resItem == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && Hooks.Active(craft ? Settings.Crafting.Value : Settings.Building.Value))
			{
				Transform obj = ((Component)elementRoot).transform.Find("res_amount");
				TMP_Text val = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null);
				if (Object.op_Implicit((Object)(object)val))
				{
					string name = req.m_resItem.m_itemData.m_shared.m_name;
					int num = ((Humanoid)player).GetInventory().CountItems(name, -1, true) + StorageNetwork.Count(name, -1, matchWorldLevel: true, ownedOnly: false);
					int num2 = req.GetAmount(quality) * craftMultiplier;
					val.text = $"{num}/{num2}";
					bool globalKey = ZoneSystem.instance.GetGlobalKey((GlobalKeys)(craft ? 25 : 24));
					((Graphic)val).color = ((num < num2 && !globalKey && Mathf.Sin(Time.time * 10f) > 0f) ? Color.red : Color.white);
				}
			}
		}
	}
	internal static class CraftFlow
	{
		private const float MaxWait = 5f;

		private static List<ChestAccess.Pull> s_pulls;

		private static float s_deadline;

		private static bool s_resume;

		private static List<Need> CurrentNeeds(InventoryGui gui, Player player)
		{
			int quality = ((gui.m_craftUpgradeItem == null) ? 1 : (gui.m_craftUpgradeItem.m_quality + 1));
			int multiplier = ((!gui.m_multiCrafting) ? 1 : gui.m_multiCraftAmount);
			return Needs.ForRecipe(player, gui.m_craftRecipe, quality, multiplier);
		}

		public static void OnCraftPressed(InventoryGui gui)
		{
			s_pulls = null;
			Player localPlayer = Player.m_localPlayer;
			if (gui.m_craftTimer < 0f || !Object.op_Implicit((Object)(object)localPlayer) || !Hooks.Active(Settings.Crafting.Value))
			{
				return;
			}
			string problem;
			List<ChestAccess.PullPlan> list = Needs.PlanShortfall(CurrentNeeds(gui, localPlayer), ((Humanoid)localPlayer).GetInventory(), out problem);
			if (list == null)
			{
				((Character)localPlayer).Message((MessageType)2, problem, 0, (Sprite)null, false);
				gui.m_craftTimer = -1f;
			}
			else if (list.Count != 0)
			{
				s_pulls = list.SelectMany((ChestAccess.PullPlan plan) => ChestAccess.Start(plan, "crafting " + ((Object)gui.m_craftRecipe).name)).ToList();
				s_deadline = Time.time + 5f;
			}
		}

		public static bool BeforeDoCrafting(InventoryGui gui, Player player)
		{
			if ((Object)(object)player != (Object)(object)Player.m_localPlayer || !Hooks.Active(Settings.Crafting.Value) || (Object)(object)gui.m_craftRecipe == (Object)null)
			{
				return true;
			}
			if (s_pulls != null && s_pulls.Any((ChestAccess.Pull p) => !p.Finished) && Time.time < s_deadline)
			{
				s_resume = true;
				return false;
			}
			s_pulls = null;
			Inventory inventory = ((Humanoid)player).GetInventory();
			foreach (Need item in CurrentNeeds(gui, player))
			{
				if (item.PayableNow(inventory) < item.Amount)
				{
					((Character)player).Message((MessageType)2, "Storage: " + Localization.instance.Localize(item.Name) + " didn't arrive from storage, crafting stopped", 0, (Sprite)null, false);
					return false;
				}
			}
			return true;
		}

		public static void AfterUpdateRecipe(InventoryGui gui)
		{
			if (s_resume)
			{
				s_resume = false;
				gui.m_craftTimer = 10000f;
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")]
	internal static class OnCraftPressedPatch
	{
		private static void Postfix(InventoryGui __instance)
		{
			CraftFlow.OnCraftPressed(__instance);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	internal static class DoCraftingPatch
	{
		[HarmonyPriority(800)]
		private static bool Prefix(InventoryGui __instance, Player player)
		{
			return CraftFlow.BeforeDoCrafting(__instance, player);
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return Hooks.ReplaceCalls(instructions, Hooks.RemoveItemByName, Hooks.Self("RemoveWithStorage"), 1, "InventoryGui.DoCrafting");
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")]
	internal static class UpdateRecipePatch
	{
		private static void Postfix(InventoryGui __instance)
		{
			CraftFlow.AfterUpdateRecipe(__instance);
		}
	}
	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	internal static class TryPlacePiecePatch
	{
		private static List<ChestAccess.Pull> s_pulls;

		private static bool Prefix(Player __instance, Piece piece, ref bool __result)
		{
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || __instance.m_noPlacementCost || !Hooks.Active(Settings.Building.Value))
			{
				return true;
			}
			List<Need> list = Needs.ForPiece(piece);
			Inventory inventory = ((Humanoid)__instance).GetInventory();
			if (list.All((Need n) => n.PayableNow(inventory) >= n.Amount))
			{
				return true;
			}
			__result = false;
			if (s_pulls != null && s_pulls.Any((ChestAccess.Pull p) => !p.Finished))
			{
				((Character)__instance).Message((MessageType)2, "Storage: still fetching materials…", 0, (Sprite)null, false);
				return false;
			}
			foreach (Need item in list)
			{
				ChestAccess.BorrowFor(item.Name);
			}
			string problem;
			List<ChestAccess.PullPlan> list2 = Needs.PlanShortfall(list, inventory, out problem);
			if (list2 == null)
			{
				((Character)__instance).Message((MessageType)2, problem, 0, (Sprite)null, false);
				return false;
			}
			s_pulls = list2.SelectMany((ChestAccess.PullPlan plan) => ChestAccess.Start(plan, "building " + ((Object)piece).name)).ToList();
			((Character)__instance).Message((MessageType)2, "Storage: fetching materials, place again in a moment", 0, (Sprite)null, false);
			return false;
		}
	}
	internal static class EpicLootCompat
	{
		public const string Guid = "randyknapp.mods.epicloot";

		private static Func<ItemData, bool> s_isMagicItem;

		private static Func<ItemData, bool> s_isMagicCraftingMaterial;

		public static bool Available { get; private set; }

		public static void Init(ManualLogSource log)
		{
			if (!Chainloader.PluginInfos.ContainsKey("randyknapp.mods.epicloot"))
			{
				return;
			}
			try
			{
				Type api = AccessTools.TypeByName("EpicLoot.API");
				s_isMagicItem = Make(api, "IsMagicItem");
				s_isMagicCraftingMaterial = Make(api, "IsMagicCraftingMaterial");
				Available = s_isMagicItem != null && s_isMagicCraftingMaterial != null;
				if (!Available)
				{
					log.LogWarning((object)"Epic Loot API not found; enchanted items in chests can't be told apart from plain ones");
				}
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("Epic Loot hookup failed; enchanted items in chests can't be told apart: " + ex.Message));
			}
		}

		private static Func<ItemData, bool> Make(Type api, string name)
		{
			MethodInfo methodInfo = ((api == null) ? null : AccessTools.Method(api, name, new Type[1] { typeof(ItemData) }, (Type[])null));
			if (!(methodInfo == null))
			{
				return (Func<ItemData, bool>)Delegate.CreateDelegate(typeof(Func<ItemData, bool>), methodInfo);
			}
			return null;
		}

		public static bool IsEnchanted(ItemData item)
		{
			if (Available && s_isMagicItem(item))
			{
				return !s_isMagicCraftingMaterial(item);
			}
			return false;
		}
	}
	internal static class Hooks
	{
		public static readonly MethodInfo CountItems = AccessTools.Method(typeof(Inventory), "CountItems", new Type[3]
		{
			typeof(string),
			typeof(int),
			typeof(bool)
		}, (Type[])null);

		public static readonly MethodInfo HaveItem = AccessTools.Method(typeof(Inventory), "HaveItem", new Type[2]
		{
			typeof(string),
			typeof(bool)
		}, (Type[])null);

		public static readonly MethodInfo GetItem = AccessTools.Method(typeof(Inventory), "GetItem", new Type[3]
		{
			typeof(string),
			typeof(int),
			typeof(bool)
		}, (Type[])null);

		public static readonly MethodInfo RemoveItemByName = AccessTools.Method(typeof(Inventory), "RemoveItem", new Type[4]
		{
			typeof(string),
			typeof(int),
			typeof(int),
			typeof(bool)
		}, (Type[])null);

		public static bool Active(bool feature)
		{
			Player localPlayer = Player.m_localPlayer;
			if (Settings.Enabled.Value && feature && Object.op_Implicit((Object)(object)localPlayer))
			{
				return !localPlayer.NoCostCheat();
			}
			return false;
		}

		private static bool IsLocalPlayers(Inventory inventory)
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return inventory == ((Humanoid)Player.m_localPlayer).m_inventory;
			}
			return false;
		}

		private static bool BuildingNow()
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return ((Character)Player.m_localPlayer).InPlaceMode();
			}
			return false;
		}

		public static int CountForCrafting(Inventory inventory, string name, int quality, bool matchWorldLevel)
		{
			return inventory.CountItems(name, quality, matchWorldLevel) + ((IsLocalPlayers(inventory) && Active(Settings.Crafting.Value)) ? StorageNetwork.Count(name, quality, matchWorldLevel, ownedOnly: false) : 0);
		}

		public static int CountForBuilding(Inventory inventory, string name, int quality, bool matchWorldLevel)
		{
			return inventory.CountItems(name, quality, matchWorldLevel) + ((IsLocalPlayers(inventory) && Active(Settings.Building.Value)) ? StorageNetwork.Count(name, quality, matchWorldLevel, ownedOnly: false) : 0);
		}

		public static bool HaveForBuilding(Inventory inventory, string name, bool matchWorldLevel)
		{
			if (!inventory.HaveItem(name, matchWorldLevel))
			{
				if (IsLocalPlayers(inventory) && Active(Settings.Building.Value))
				{
					return StorageNetwork.Count(name, -1, matchWorldLevel, ownedOnly: false) > 0;
				}
				return false;
			}
			return true;
		}

		public static ItemData GetItemForCrafting(Inventory inventory, string name, int quality, bool isPrefabName)
		{
			ItemData item = inventory.GetItem(name, quality, isPrefabName);
			if (item != null || isPrefabName || !IsLocalPlayers(inventory) || !Active(Settings.Crafting.Value))
			{
				return item;
			}
			return StorageNetwork.FindItem(name, quality);
		}

		public static void RemoveWithStorage(Inventory inventory, string name, int amount, int itemQuality, bool worldLevelBased)
		{
			bool feature = (BuildingNow() ? Settings.Building.Value : Settings.Crafting.Value);
			if (!IsLocalPlayers(inventory) || !Active(feature))
			{
				inventory.RemoveItem(name, amount, itemQuality, worldLevelBased);
				return;
			}
			int num = inventory.CountItems(name, itemQuality, worldLevelBased);
			inventory.RemoveItem(name, amount, itemQuality, worldLevelBased);
			int num2 = amount - (num - inventory.CountItems(name, itemQuality, worldLevelBased));
			if (num2 > 0)
			{
				num2 -= ChestAccess.TakeFromOwned(name, num2, itemQuality, worldLevelBased, BuildingNow() ? "building" : "crafting");
				if (num2 > 0)
				{
					StorageLog.Warning($"{num2} {name} couldn't be taken from storage; that part of the cost was free");
				}
			}
		}

		public static IEnumerable<CodeInstruction> ReplaceCalls(IEnumerable<CodeInstruction> instructions, MethodInfo target, MethodInfo replacement, int expected, string where)
		{
			int replaced = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (target != null && CodeInstructionExtensions.Calls(instruction, target))
				{
					replaced++;
					yield return new CodeInstruction(OpCodes.Call, (object)replacement)
					{
						labels = instruction.labels,
						blocks = instruction.blocks
					};
				}
				else
				{
					yield return instruction;
				}
			}
			if (replaced != expected)
			{
				Debug.LogWarning((object)(string.Format("[CraftFromStorage] {0}: expected {1} call(s) to {2}, replaced {3}. ", where, expected, target?.Name ?? "a missing method", replaced) + "Nearby storage may not be counted there."));
			}
		}

		public static MethodInfo Self(string name)
		{
			return AccessTools.Method(typeof(Hooks), name, (Type[])null, (Type[])null);
		}
	}
	internal static class Hotkeys
	{
		private static readonly KeyCode[] ModifierKeys;

		private static readonly MethodInfo s_keyCodeToPath;

		public static bool Pressed(ConfigEntry<KeyboardShortcut> entry)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = entry.Value;
			if ((int)((KeyboardShortcut)(ref value)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey, false))
			{
				return false;
			}
			List<KeyCode> modifiers = ((KeyboardShortcut)(ref value)).Modifiers.ToList();
			if (modifiers.Any((KeyCode key) => !ZInput.GetKey(key, false)))
			{
				return false;
			}
			return !ModifierKeys.Any((KeyCode key) => !modifiers.Contains(key) && ZInput.GetKey(key, false));
		}

		public static bool IsBoundToUse(KeyCode key)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			if ((int)key == 0 || ZInput.instance == null || s_keyCodeToPath == null)
			{
				return false;
			}
			ButtonDef buttonDef = ZInput.instance.GetButtonDef("Use");
			if (((buttonDef != null) ? buttonDef.ButtonAction : null) == null)
			{
				return false;
			}
			string text = s_keyCodeToPath.Invoke(null, new object[2] { key, false }) as string;
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			InputControl val = InputSystem.FindControl(text);
			Enumerator<InputBinding> enumerator = buttonDef.ButtonAction.bindings.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					InputBinding current = enumerator.Current;
					if (!string.IsNullOrEmpty(((InputBinding)(ref current)).effectivePath))
					{
						if (string.Equals(((InputBinding)(ref current)).effectivePath, text, StringComparison.OrdinalIgnoreCase))
						{
							return true;
						}
						if (val != null && InputControlPath.Matches(((InputBinding)(ref current)).effectivePath, val))
						{
							return true;
						}
					}
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			return false;
		}

		public static bool IsTyping()
		{
			if (!Console.IsVisible() && !TextInput.IsVisible() && !Menu.IsVisible() && !Minimap.InTextInput())
			{
				if (Object.op_Implicit((Object)(object)Chat.instance))
				{
					return Chat.instance.HasFocus();
				}
				return false;
			}
			return true;
		}

		static Hotkeys()
		{
			KeyCode[] array = new KeyCode[6];
			RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
			ModifierKeys = (KeyCode[])(object)array;
			s_keyCodeToPath = AccessTools.Method(typeof(ZInput), "KeyCodeToPath", (Type[])null, (Type[])null);
		}
	}
	public enum MucState
	{
		Unknown,
		Healthy,
		Missing,
		TooOld,
		PatchesMissing
	}
	public static class MucHealth
	{
		public const string Guid = "com.maxsch.valheim.MultiUserChest";

		public static readonly Version MinVersion = new Version(0, 6, 2);

		public static MucState State { get; private set; } = MucState.Unknown;

		public static string Problem { get; private set; } = "";

		public static bool CanUseNonOwnedChests => State == MucState.Healthy;

		private static IEnumerable<(string Name, MethodBase Method, Func<Patches, IEnumerable<Patch>> Kind)> RequiredPatches()
		{
			yield return (Name: "Inventory.AddItem(item, amount, x, y)", Method: AccessTools.Method(typeof(Inventory), "AddItem", new Type[5]
			{
				typeof(ItemData),
				typeof(int),
				typeof(int),
				typeof(int),
				typeof(bool)
			}, (Type[])null), Kind: (Patches p) => p.Prefixes);
			yield return (Name: "Inventory.AddItem(item)", Method: AccessTools.Method(typeof(Inventory), "AddItem", new Type[1] { typeof(ItemData) }, (Type[])null), Kind: (Patches p) => p.Prefixes);
			yield return (Name: "Inventory.RemoveItem(item)", Method: AccessTools.Method(typeof(Inventory), "RemoveItem", new Type[1] { typeof(ItemData) }, (Type[])null), Kind: (Patches p) => p.Prefixes);
			yield return (Name: "Container.RPC_RequestOpen", Method: AccessTools.Method(typeof(Container), "RPC_RequestOpen", (Type[])null, (Type[])null), Kind: (Patches p) => p.Prefixes);
			yield return (Name: "InventoryGui.UpdateContainer", Method: AccessTools.Method(typeof(InventoryGui), "UpdateContainer", (Type[])null, (Type[])null), Kind: (Patches p) => p.Transpilers);
		}

		public static void Check(ManualLogSource log)
		{
			(State, Problem) = Evaluate();
			if (State == MucState.Healthy)
			{
				log.LogInfo((object)string.Format("MultiUserChest {0} found and healthy: chests other players own can be used", Chainloader.PluginInfos["com.maxsch.valheim.MultiUserChest"].Metadata.Version));
			}
			else
			{
				log.LogWarning((object)(Problem + ". Only chests you own will be used (in multiplayer that's usually the ones you opened last)."));
			}
		}

		private static (MucState, string) Evaluate()
		{
			if (!Chainloader.PluginInfos.TryGetValue("com.maxsch.valheim.MultiUserChest", out var value))
			{
				return (MucState.Missing, "MultiUserChest isn't installed");
			}
			Version version = value.Metadata.Version;
			if (version < MinVersion)
			{
				return (MucState.TooOld, $"MultiUserChest {version} is older than {MinVersion}");
			}
			List<string> list = new List<string>();
			foreach (var (text, methodBase, func) in RequiredPatches())
			{
				if (methodBase == null)
				{
					list.Add(text + " (method not found in this game version)");
					continue;
				}
				Patches patchInfo = Harmony.GetPatchInfo(methodBase);
				if (patchInfo == null || !func(patchInfo).Any((Patch p) => p.owner == "com.maxsch.valheim.MultiUserChest"))
				{
					list.Add(text);
				}
			}
			if (list.Count > 0)
			{
				return (MucState.PatchesMissing, string.Format("MultiUserChest {0} is loaded but its patches are missing on {1} ", version, string.Join(", ", list)) + "(another mod may have broken its patching; see MUC issue #19)");
			}
			return (MucState.Healthy, "");
		}
	}
	internal static class MucInternals
	{
		private static MethodInfo s_get;

		private static MethodInfo s_isSlotBlocked;

		private static MethodInfo s_releaseSlot;

		public static bool Available { get; private set; }

		public static void Init(ManualLogSource log)
		{
			if (MucHealth.State == MucState.Healthy)
			{
				try
				{
					Type type = AccessTools.TypeByName("MultiUserChest.InventoryBlock");
					s_get = ((type == null) ? null : AccessTools.Method(type, "Get", new Type[1] { typeof(Inventory) }, (Type[])null));
					s_isSlotBlocked = ((type == null) ? null : AccessTools.Method(type, "IsSlotBlocked", new Type[1] { typeof(Vector2i) }, (Type[])null));
					s_releaseSlot = ((type == null) ? null : AccessTools.Method(type, "ReleaseSlot", new Type[1] { typeof(Vector2i) }, (Type[])null));
					Available = s_get != null && s_isSlotBlocked != null && s_releaseSlot != null;
				}
				catch (Exception ex)
				{
					log.LogWarning((object)("MUC slot-block lookup failed: " + ex.Message));
				}
				if (!Available)
				{
					log.LogWarning((object)"Couldn't find MUC's slot blocking; a fetch that never gets a reply may stop auto-pickup until you relog");
				}
			}
		}

		public static bool IsSlotBlocked(Inventory inventory, Vector2i slot)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (!Available)
			{
				return false;
			}
			object obj = s_get.Invoke(null, new object[1] { inventory });
			return (bool)s_isSlotBlocked.Invoke(obj, new object[1] { slot });
		}

		public static void ReleaseSlot(Inventory inventory, Vector2i slot)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			if (Available)
			{
				object obj = s_get.Invoke(null, new object[1] { inventory });
				s_releaseSlot.Invoke(obj, new object[1] { slot });
			}
		}
	}
	internal readonly struct Need
	{
		public readonly string Name;

		public readonly int Amount;

		public readonly int Quality;

		public Need(string name, int amount, int quality)
		{
			Name = name;
			Amount = amount;
			Quality = quality;
		}

		public int PayableNow(Inventory inventory)
		{
			return inventory.CountItems(Name, Quality, true) + StorageNetwork.Count(Name, Quality, matchWorldLevel: true, ownedOnly: true);
		}
	}
	internal static class Needs
	{
		public static List<Need> ForRecipe(Player player, Recipe recipe, int quality, int multiplier)
		{
			List<Need> list = new List<Need>();
			if (!Object.op_Implicit((Object)(object)recipe) || ZoneSystem.instance.GetGlobalKey((GlobalKeys)25))
			{
				return list;
			}
			if (recipe.m_requireOnlyOneIngredient)
			{
				int num = default(int);
				ItemData val = default(ItemData);
				recipe.GetAmount(quality, ref num, ref val, multiplier);
				if (val != null && num > 0)
				{
					list.Add(new Need(val.m_shared.m_name, num, val.m_quality));
				}
				return list;
			}
			CraftingStation currentCraftingStation = player.GetCurrentCraftingStation();
			Requirement[] resources = recipe.m_resources;
			foreach (Requirement val2 in resources)
			{
				if ((!((Object)(object)currentCraftingStation != (Object)null) || currentCraftingStation.m_upgrader == val2.m_upgraderResource) && (!((Object)(object)currentCraftingStation == (Object)null) || !val2.m_upgraderResource) && Object.op_Implicit((Object)(object)val2.m_resItem))
				{
					int num2 = val2.GetAmount(quality) * multiplier;
					if (num2 > 0)
					{
						Add(list, val2.m_resItem.m_itemData.m_shared.m_name, num2);
					}
				}
			}
			return list;
		}

		public static List<Need> ForPiece(Piece piece)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			List<Need> list = new List<Need>();
			if (!Object.op_Implicit((Object)(object)piece) || ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey()))
			{
				return list;
			}
			Requirement[] resources = piece.m_resources;
			foreach (Requirement val in resources)
			{
				if (Object.op_Implicit((Object)(object)val.m_resItem) && val.m_amount > 0)
				{
					Add(list, val.m_resItem.m_itemData.m_shared.m_name, val.m_amount);
				}
			}
			return list;
		}

		private static void Add(List<Need> needs, string name, int amount)
		{
			for (int i = 0; i < needs.Count; i++)
			{
				if (!(needs[i].Name != name))
				{
					needs[i] = new Need(name, needs[i].Amount + amount, -1);
					return;
				}
			}
			needs.Add(new Need(name, amount, -1));
		}

		public static List<ChestAccess.PullPlan> PlanShortfall(List<Need> needs, Inventory inventory, out string problem)
		{
			problem = null;
			List<ChestAccess.PullPlan> list = new List<ChestAccess.PullPlan>();
			ChestAccess.SlotReservation slots = new ChestAccess.SlotReservation();
			int num = 0;
			foreach (Need need in needs)
			{
				int num2 = need.Amount - need.PayableNow(inventory);
				if (num2 > 0)
				{
					ChestAccess.PullPlan pullPlan = ChestAccess.PlanPulls(need.Name, need.Quality, num2, slots);
					if (pullPlan.Missing > 0)
					{
						problem = "Storage: not enough " + Localization.instance.Localize(need.Name) + " in reach";
						return null;
					}
					num += pullPlan.SlotsShort;
					list.Add(pullPlan);
				}
			}
			if (num > 0)
			{
				problem = string.Format("Storage: need {0} free inventory slot{1} to fetch materials from chests other players have", num, (num == 1) ? "" : "s");
				return null;
			}
			return list;
		}
	}
	[BepInPlugin("s992.CraftFromStorage", "CraftFromStorage", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInIncompatibility("toxo.craftfromchests")]
	public class Plugin : BaseUnityPlugin
	{
		private Harmony m_harmony;

		private void Awake()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			Settings.Bind(((BaseUnityPlugin)this).Config);
			StorageLog.Init(((BaseUnityPlugin)this).Logger);
			EpicLootCompat.Init(((BaseUnityPlugin)this).Logger);
			m_harmony = new Harmony("s992.CraftFromStorage");
			m_harmony.PatchAll();
			((Component)this).gameObject.AddComponent<Runner>();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"CraftFromStorage 1.0.0 loaded");
		}

		private void Start()
		{
			MucHealth.Check(((BaseUnityPlugin)this).Logger);
			MucInternals.Init(((BaseUnityPlugin)this).Logger);
		}
	}
	internal static class Status
	{
		public static string Line()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			string text = ((object)Settings.ToggleKey.Value/*cast due to .constrained prefix*/).ToString();
			if (!Settings.Enabled.Value)
			{
				return "Storage: off [" + text + "]";
			}
			int count = StorageNetwork.Usable.Count;
			List<string> list = new List<string>();
			list.Add(string.Format("Storage [{0}]: {1} chest{2} within {3:0} m", text, count, (count == 1) ? "" : "s", StorageNetwork.EffectiveRange));
			List<string> list2 = list;
			if (StorageNetwork.RangeCapped)
			{
				list2.Add("range capped by loaded area");
			}
			if (StorageNetwork.SkippedNoOwner > 0)
			{
				list2.Add($"{StorageNetwork.SkippedNoOwner} skipped (no owner)");
			}
			if (StorageNetwork.InUseByOthers > 0)
			{
				list2.Add($"{StorageNetwork.InUseByOthers} in use");
			}
			if (StorageNetwork.Unresponsive > 0)
			{
				list2.Add($"{StorageNetwork.Unresponsive} not answering");
			}
			if (ChestAccess.PendingPulls > 0)
			{
				list2.Add($"fetching {ChestAccess.PendingPulls}");
			}
			if (!MucHealth.CanUseNonOwnedChests && !StorageNetwork.IsSinglePlayer())
			{
				list2.Add("only your chests (MultiUserChest missing or broken)");
			}
			return string.Join(" · ", list2);
		}
	}
	internal sealed class Runner : MonoBehaviour
	{
		private bool m_checkChangeKey = true;

		private const float BorrowAheadInterval = 1f;

		private float m_nextBorrowAhead;

		private GUIStyle m_style;

		private GUIStyle m_shadow;

		public static Runner Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
			Settings.ChangeMaterialKey.SettingChanged += delegate
			{
				m_checkChangeKey = true;
			};
		}

		private static bool RejectIfUseKey()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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)
			KeyboardShortcut value = Settings.ChangeMaterialKey.Value;
			KeyCode mainKey = ((KeyboardShortcut)(ref value)).MainKey;
			if (!Hotkeys.IsBoundToUse(mainKey))
			{
				return false;
			}
			Settings.ChangeMaterialKey.Value = KeyboardShortcut.Empty;
			string text = $"Change material key can't be your Use key ({mainKey}); it's back to Alt Place + Use";
			StorageLog.Warning(text);
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				((Character)Player.m_localPlayer).Message((MessageType)1, text, 0, (Sprite)null, false);
			}
			return true;
		}

		private void Update()
		{
			if (m_checkChangeKey && ZInput.instance != null)
			{
				m_checkChangeKey = false;
				RejectIfUseKey();
			}
			ChestAccess.Tick();
			Player localPlayer = Player.m_localPlayer;
			if (Object.op_Implicit((Object)(object)localPlayer) && !Hotkeys.IsTyping())
			{
				if (Hotkeys.Pressed(Settings.ToggleKey))
				{
					Settings.Enabled.Value = !Settings.Enabled.Value;
					StorageNetwork.Invalidate();
					((Character)localPlayer).Message((MessageType)1, "Use nearby storage: " + (Settings.Enabled.Value ? "on" : "off"), 0, (Sprite)null, false);
				}
				if (!SmeltFlow.UsesAltUse && Hotkeys.Pressed(Settings.ChangeMaterialKey) && Hooks.Active(Settings.Smelting.Value) && !RejectIfUseKey())
				{
					SmeltFlow.CycleOnHover(localPlayer);
				}
				if (Hotkeys.Pressed(Settings.BlocklistKey) && Hooks.Active(Settings.Smelting.Value))
				{
					SmeltFlow.ToggleBlocklistOnHover(localPlayer);
				}
				if (Time.time >= m_nextBorrowAhead)
				{
					m_nextBorrowAhead = Time.time + 1f;
					BorrowAhead(localPlayer);
					SmeltFlow.Prune();
					SmelterInFlight.Prune();
					StorageNetwork.Prune();
				}
			}
		}

		private static void BorrowAhead(Player player)
		{
			if (!Settings.Enabled.Value)
			{
				return;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			List<Need> list = null;
			if (((Character)player).InPlaceMode())
			{
				if (Settings.Building.Value)
				{
					list = Needs.ForPiece(player.GetSelectedPiece());
				}
			}
			else if (InventoryGui.IsVisible() && Settings.Crafting.Value && Object.op_Implicit((Object)(object)((RecipeDataPair)(ref InventoryGui.instance.m_selectedRecipe)).Recipe))
			{
				InventoryGui instance = InventoryGui.instance;
				ItemData itemData = ((RecipeDataPair)(ref instance.m_selectedRecipe)).ItemData;
				list = Needs.ForRecipe(player, ((RecipeDataPair)(ref instance.m_selectedRecipe)).Recipe, (itemData == null) ? 1 : (itemData.m_quality + 1), 1);
			}
			if (list == null)
			{
				return;
			}
			foreach (Need item in list)
			{
				if (item.PayableNow(inventory) < item.Amount)
				{
					ChestAccess.BorrowFor(item.Name);
				}
			}
		}

		private static bool StatusContext(Player player)
		{
			if (!((Character)player).InPlaceMode() && (!InventoryGui.IsVisible() || !((Object)(object)InventoryGui.instance.m_currentContainer == (Object)null)))
			{
				return Object.op_Implicit((Object)(object)SmeltFlow.HoveredOreSwitchSmelter(player));
			}
			return true;
		}

		private void OnGUI()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			//IL_007a: 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_0095: Expected O, but got Unknown
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			if ((int)Event.current.type != 7 || !Settings.ShowStatus.Value)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (Object.op_Implicit((Object)(object)localPlayer) && StatusContext(localPlayer))
			{
				if (m_style == null)
				{
					m_style = new GUIStyle(GUI.skin.label)
					{
						fontSize = 15,
						alignment = (TextAnchor)1
					};
					m_style.normal.textColor = new Color(1f, 0.9f, 0.7f);
					m_shadow = new GUIStyle(m_style);
					m_shadow.normal.textColor = new Color(0f, 0f, 0f, 0.8f);
				}
				string text = Status.Line();
				Rect val = default(Rect);
				((Rect)(ref val))..ctor(0f, 8f, (float)Screen.width, 24f);
				GUI.Label(new Rect(((Rect)(ref val)).x + 1f, ((Rect)(ref val)).y + 1f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), text, m_shadow);
				GUI.Label(val, text, m_style);
			}
		}
	}
	internal static class Settings
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<KeyboardShortcut> ToggleKey;

		public static ConfigEntry<int> Range;

		public static ConfigEntry<bool> ShowStatus;

		public static ConfigEntry<bool> Crafting;

		public static ConfigEntry<bool> Building;

		public static ConfigEntry<bool> Smelting;

		public static ConfigEntry<bool> CartsAndShips;

		public static ConfigEntry<string> ExcludedContainers;

		public static ConfigEntry<string> SmeltBlocklist;

		public static ConfigEntry<KeyboardShortcut> BlocklistKey;

		public static ConfigEntry<KeyboardShortcut> ChangeMaterialKey;

		public static ConfigEntry<string> SmeltSelections;

		public static ConfigEntry<bool> Borrow;

		public static ConfigEntry<bool> LogActions;

		public static int Version { get; private set; }

		public static HashSet<string> Excluded { get; private set; } = new HashSet<string>();

		public static HashSet<string> Blocklist { get; private set; } = new HashSet<string>();

		public static void Bind(ConfigFile config)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			Enabled = config.Bind<bool>("1. General", "Use nearby storage", true, "Crafting, building and smelting use materials from nearby chests. The toggle key flips this.");
			ToggleKey = config.Bind<KeyboardShortcut>("1. General", "Toggle key", new KeyboardShortcut((KeyCode)278, Array.Empty<KeyCode>()), "Turns \"use nearby storage\" on and off.");
			Range = config.Bind<int>("1. General", "Range", 30, new ConfigDescription("Chests within this many metres of you are used. It's capped at the distance the game guarantees chests are loaded (128 m on the default simulation distance); the status line says when the cap applies.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 250), Array.Empty<object>()));
			ShowStatus = config.Bind<bool>("1. General", "Show status line", true, "Shows a line at the top of the screen while crafting, building or hovering a smelter: how many chests are used, how many were skipped and why.");
			Crafting = config.Bind<bool>("2. Features", "Crafting", true, "Crafting stations (and upgrades) use nearby storage.");
			Building = config.Bind<bool>("2. Features", "Building", true, "Building pieces use nearby storage.");
			Smelting = config.Bind<bool>("2. Features", "Smelting", true, "Smelters, furnaces, kilns, windmills, spinning wheels and eitr refineries take materials and fuel from nearby storage.");
			CartsAndShips = config.Bind<bool>("3. Chests", "Carts and ships", false, "Carts and ships nearby count as storage.");
			ExcludedContainers = config.Bind<string>("3. Chests", "Excluded containers", "", "Container prefab names that never count as storage, comma separated (e.g. piece_chest_private).");
			SmeltBlocklist = config.Bind<string>("4. Smelting", "Blocklist", "FineWood,RoundLog,YggdrasilWood", "Item prefab names that are never put into a smelter automatically, from your inventory or storage, comma separated. Using an item from your hotbar on a smelter still works. The blocklist key toggles the material shown when hovering a smelter.");
			BlocklistKey = config.Bind<KeyboardShortcut>("4. Smelting", "Blocklist key", new KeyboardShortcut((KeyCode)279, Array.Empty<KeyCode>()), "While hovering a smelter's input, adds or removes its selected material from the blocklist.");
			ChangeMaterialKey = config.Bind<KeyboardShortcut>("4. Smelting", "Change material key", KeyboardShortcut.Empty, "While hovering a smelter's input, switches which material goes in next. Empty: the game's Alt Place + Use (Shift+E by default; also works on a gamepad). Your Use key is refused (it would also insert).");
			SmeltSelections = config.Bind<string>("4. Smelting", "Selected materials", "", "Managed by the mod: which material each kind of smelter takes (changed in game with the change material key).");
			Borrow = config.Bind<bool>("5. Diagnostics", "Borrow chests", true, "Ask other players for ownership of nearby chests before they're needed, so crafting and building can take from them instantly. Off: those chests are only reached by fetching items into your inventory.");
			LogActions = config.Bind<bool>("5. Diagnostics", "Log actions", false, "Writes every item the mod takes from or fetches out of a chest to BepInEx/CraftFromStorage.log. Handy when something looks wrong; the file is never trimmed, so leave it off otherwise. `storage audit` writes there either way.");
			Parse();
			config.SettingChanged += delegate
			{
				Parse();
			};
		}

		private static void Parse()
		{
			Excluded = SplitNames(ExcludedContainers.Value);
			Blocklist = SplitNames(SmeltBlocklist.Value);
			Version++;
		}

		public static HashSet<string> SplitNames(string value)
		{
			return new HashSet<string>(from s in (value ?? "").Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select s.Trim() into s
				where s.Length > 0
				select s, StringComparer.OrdinalIgnoreCase);
		}

		public static void ToggleBlocklisted(string prefabName)
		{
			HashSet<string> hashSet = SplitNames(SmeltBlocklist.Value);
			if (!hashSet.Remove(prefabName))
			{
				hashSet.Add(prefabName);
			}
			SmeltBlocklist.Value = string.Join(",", hashSet);
		}

		public static string GetSelection(string smelterPrefab)
		{
			string[] array = (SmeltSelections.Value ?? "").Split(';');
			foreach (string text in array)
			{
				int num = text.IndexOf('=');
				if (num > 0 && text.Substring(0, num) == smelterPrefab)
				{
					return text.Substring(num + 1);
				}
			}
			return null;
		}

		public static void SetSelection(string smelterPrefab, string itemPrefab)
		{
			List<string> list = (from p in (SmeltSelections.Value ?? "").Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries)
				where !p.StartsWith(smelterPrefab + "=", StringComparison.Ordinal)
				select p).ToList();
			list.Add(smelterPrefab + "=" + itemPrefab);
			SmeltSelections.Value = string.Join(";", list);
		}
	}
	internal static class SmelterInFlight
	{
		private sealed class Channel
		{
			private int m_pending;

			private float m_lastSeen = float.NaN;

			private float m_expires;

			public float Effective(float observed)
			{
				Catch(observed);
				return observed + (float)m_pending;
			}

			public void Sent(float observed)
			{
				Catch(observed);
				m_pending++;
				m_expires = Time.time + 3f;
			}

			private void Catch(float observed)
			{
				if (!float.IsNaN(m_lastSeen))
				{
					float num = observed - m_lastSeen;
					if (num >= 0.5f)
					{
						m_pending = Mathf.Max(0, m_pending - Mathf.RoundToInt(num));
					}
				}
				m_lastSeen = observed;
				if (m_pending > 0 && Time.time > m_expires)
				{
					m_pending = 0;
				}
			}
		}

		private const float Expiry = 3f;

		private static readonly Dictionary<Smelter, (Channel Ore, Channel Fuel)> s_channels = new Dictionary<Smelter, (Channel, Channel)>();

		private static (Channel Ore, Channel Fuel) For(Smelter smelter)
		{
			if (!s_channels.TryGetValue(smelter, out (Channel, Channel) value))
			{
				value = (s_channels[smelter] = (new Channel(), new Channel()));
			}
			return value;
		}

		public static int Queue(Smelter smelter)
		{
			return Mathf.RoundToInt(For(smelter).Ore.Effective(smelter.GetQueueSize()));
		}

		public static float Fuel(Smelter smelter)
		{
			return For(smelter).Fuel.Effective(smelter.GetFuel());
		}

		public static void OreSent(Smelter smelter)
		{
			if (!smelter.m_nview.IsOwner())
			{
				For(smelter).Ore.Sent(smelter.GetQueueSize());
			}
		}

		public static void FuelSent(Smelter smelter)
		{
			if (!smelter.m_nview.IsOwner())
			{
				For(smelter).Fuel.Sent(smelter.GetFuel());
			}
		}

		public static void Prune()
		{
			foreach (Smelter item in s_channels.Keys.Where((Smelter s) => !Object.op_Implicit((Object)(object)s)).ToList())
			{
				s_channels.Remove(item);
			}
		}
	}
	internal static class SmeltFlow
	{
		public sealed class Material
		{
			public string Prefab;

			public string Name;

			public ItemDrop Drop;
		}

		private static readonly Dictionary<Smelter, List<ChestAccess.Pull>> s_pulls = new Dictionary<Smelter, List<ChestAccess.Pull>>();

		private static readonly HashSet<string> s_reportedDuplicates = new HashSet<string>();

		public static bool UsesAltUse
		{
			get
			{
				//IL_0005: Unknown result type (might be due to invalid IL or missing references)
				//IL_000a: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0013: Invalid comparison between Unknown and I4
				KeyboardShortcut value = Settings.ChangeMaterialKey.Value;
				return (int)((KeyboardShortcut)(ref value)).MainKey == 0;
			}
		}

		public static string SmelterPrefab(Smelter smelter)
		{
			return StorageNetwork.PrefabName(((Component)smelter).gameObject);
		}

		public static List<Material> Materials(Smelter smelter)
		{
			List<IGrouping<string, ItemConversion>> list = (from c in smelter.m_conversion
				where (Object)(object)c.m_from != (Object)null
				group c by ((Object)((Component)c.m_from).gameObject).name).ToList();
			if (list.Count != smelter.m_conversion.Count((ItemConversion c) => (Object)(object)c.m_from != (Object)null) && s_reportedDuplicates.Add(SmelterPrefab(smelter)))
			{
				StorageLog.Info(SmelterPrefab(smelter) + " lists the same input more than once: " + string.Join(", ", from g in list
					where g.Count() > 1
					select $"{g.Key} x{g.Count()}"));
			}
			return list.Select((IGrouping<string, ItemConversion> g) => new Material
			{
				Prefab = g.Key,
				Name = g.First().m_from.m_itemData.m_shared.m_name,
				Drop = g.First().m_from
			}).ToList();
		}

		private static int PositionOf(List<Material> options, Material material)
		{
			if (material != null)
			{
				return options.FindIndex((Material m) => m.Prefab == material.Prefab);
			}
			return -1;
		}

		public static int InInventory(string name)
		{
			return ((Humanoid)Player.m_localPlayer).GetInventory().CountItems(name, -1, true);
		}

		public static int InStorage(string name)
		{
			return StorageNetwork.Count(name, -1, matchWorldLevel: true, ownedOnly: false);
		}

		private static int Available(Material m)
		{
			return InInventory(m.Name) + InStorage(m.Name);
		}

		public static bool Blocklisted(string prefab)
		{
			return Settings.Blocklist.Contains(prefab);
		}

		public static Material Selected(Smelter smelter, List<Material> materials)
		{
			string chosen = Settings.GetSelection(SmelterPrefab(smelter));
			Material material = materials.FirstOrDefault((Material m) => m.Prefab == chosen);
			if (material != null)
			{
				return material;
			}
			return materials.FirstOrDefault((Material m) => !Blocklisted(m.Prefab) && Available(m) > 0) ?? materials.FirstOrDefault((Material m) => !Blocklisted(m.Prefab));
		}

		public static void Cycle(Smelter smelter, Humanoid user)
		{
			List<Material> list = Materials(smelter);
			Material current = Selected(smelter, list);
			List<Material> list2 = list.Where((Material m) => Available(m) > 0 || m == current).ToList();
			if (list2.Count == 0)
			{
				((Character)user).Message((MessageType)2, "$msg_noprocessableitems", 0, (Sprite)null, false);
				return;
			}
			Material material = list2[(PositionOf(list2, current) + 1) % list2.Count];
			Settings.SetSelection(SmelterPrefab(smelter), material.Prefab);
			((Character)user).Message((MessageType)1, smelter.m_name + ": " + material.Name + (Blocklisted(material.Prefab) ? " (blocklisted)" : ""), 0, (Sprite)null, false);
		}

		public static string HoverLines(Smelter smelter)
		{
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			List<Material> list = Materials(smelter);
			Material selected = Selected(smelter, list);
			if (selected == null)
			{
				return "";
			}
			if (InInventory(selected.Name) + StorageNetwork.Count(selected.Name, -1, matchWorldLevel: true, ownedOnly: true) == 0)
			{
				ChestAccess.BorrowFor(selected.Name);
			}
			List<Material> list2 = list.Where((Material m) => Available(m) > 0 || m == selected).ToList();
			string text = (Blocklisted(selected.Prefab) ? $" <color=orange>blocklisted</color> ({Settings.BlocklistKey.Value} to allow)" : $" — {InInventory(selected.Name)} in inventory, {InStorage(selected.Name)} in storage");
			string text2 = "\nAdding: " + selected.Name + text;
			if (list2.Count > 1)
			{
				text2 += $"\n[<color=yellow><b>{ChangeKeyLabel()}</b></color>] change ({PositionOf(list2, selected) + 1}/{list2.Count})";
			}
			return Localization.instance.Localize(text2);
		}

		public static bool BeforeAddOre(Smelter smelter, Humanoid user, ref ItemData item, ref bool result)
		{
			if ((Object)(object)user == (Object)(object)Player.m_localPlayer && Settings.Smelting.Value && SmelterInFlight.Queue(smelter) >= smelter.m_maxOre)
			{
				((Character)user).Message((MessageType)2, "$msg_itsfull", 0, (Sprite)null, false);
				result = false;
				return false;
			}
			if (item != null || (Object)(object)user != (Object)(object)Player.m_localPlayer || !Hooks.Active(Settings.Smelting.Value))
			{
				return true;
			}
			List<Material> list = Materials(smelter);
			if (list.Count == 0)
			{
				return true;
			}
			Material material = Selected(smelter, list);
			result = false;
			if (material == null)
			{
				((Character)user).Message((MessageType)2, "Storage: everything this takes is blocklisted", 0, (Sprite)null, false);
				return false;
			}
			if (Blocklisted(material.Prefab))
			{
				((Character)user).Message((MessageType)2, "Storage: " + Localization.instance.Localize(material.Name) + " is blocklisted", 0, (Sprite)null, false);
				return false;
			}
			ItemData item2 = user.GetInventory().GetItem(material.Name, -1, false);
			if (item2 != null)
			{
				item = item2;
				return true;
			}
			if (ChestAccess.TakeFromOwned(material.Name, 1, -1, worldLevelBased: true, smelter.m_name ?? "") == 1)
			{
				item = material.Drop.m_itemData.Clone();
				item.m_dropPrefab = ((Component)material.Drop).gameObject;
				item.m_stack = 2;
				return true;
			}
			return FetchBatch(smelter, user, material.Name, smelter.m_maxOre - SmelterInFlight.Queue(smelter));
		}

		public static bool BeforeAddFuel(Smelter smelter, Humanoid user, ItemData item, ref bool result)
		{
			if ((Object)(object)user == (Object)(object)Player.m_localPlayer && Settings.Smelting.Value && Object.op_Implicit((Object)(object)smelter.m_fuelItem) && SmelterInFlight.Fuel(smelter) > (float)(smelter.m_maxFuel - 1))
			{
				((Character)user).Message((MessageType)2, "$msg_itsfull", 0, (Sprite)null, false);
				result = false;
				return false;
			}
			if (item != null || (Object)(object)user != (Object)(object)Player.m_localPlayer || !Hooks.Active(Settings.Smelting.Value) || !Object.op_Implicit((Object)(object)smelter.m_fuelItem))
			{
				return true;
			}
			string name = smelter.m_fuelItem.m_itemData.m_shared.m_name;
			if (user.GetInventory().HaveItem(name, true) || Blocklisted(((Object)((Component)smelter.m_fuelItem).gameObject).name) || InStorage(name) == 0)
			{
				return true;
			}
			result = false;
			if (ChestAccess.TakeFromOwned(name, 1, -1, worldLevelBased: true, smelter.m_name + " fuel") == 1)
			{
				((Character)user).Message((MessageType)2, "$msg_added " + name, 0, (Sprite)null, false);
				smelter.m_nview.InvokeRPC("RPC_AddFuel", Array.Empty<object>());
				result = true;
				return false;
			}
			return FetchBatch(smelter, user, name, smelter.m_maxFuel - Mathf.CeilToInt(SmelterInFlight.Fuel(smelter)));
		}

		private static bool FetchBatch(Smelter smelter, Humanoid user, string name, int wanted)
		{
			if (s_pulls.TryGetValue(smelter, out var value) && value.Any((ChestAccess.Pull p) => !p.Finished))
			{
				((Character)user).Message((MessageType)2, "Storage: still fetching…", 0, (Sprite)null, false);
				return false;
			}
			int num = Mathf.Min(wanted, InStorage(name));
			if (num <= 0)
			{
				((Character)user).Message((MessageType)2, "$msg_donthaveany " + name, 0, (Sprite)null, false);
				return false;
			}
			ChestAccess.SlotReservation slots = new ChestAccess.SlotReservation();
			ChestAccess.PullPlan pullPlan = ChestAccess.PlanPulls(name, -1, num, slots);
			if (pullPlan.Steps.Count == 0)
			{
				((Character)user).Message((MessageType)2, (pullPlan.SlotsShort > 0) ? "Storage: need 1 free inventory slot to fetch from chests other players have" : ("$msg_donthaveany " + name), 0, (Sprite)null, false);
				return false;
			}
			s_pulls[smelter] = ChestAccess.Start(pullPlan, smelter.m_name);
			((Character)user).Message((MessageType)2, $"Storage: fetching {pullPlan.Total} {Localization.instance.Localize(name)}", 0, (Sprite)null, false);
			return false;
		}

		private static string ChangeKeyLabel()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			if (!UsesAltUse)
			{
				return ((object)Settings.ChangeMaterialKey.Value/*cast due to .constrained prefix*/).ToString();
			}
			return "$KEY_AltPlace + $KEY_Use";
		}

		public static void CycleOnHover(Player player)
		{
			Smelter val = HoveredOreSwitchSmelter(player);
			if (Object.op_Implicit((Object)(object)val))
			{
				Cycle(val, (Humanoid)(object)player);
			}
		}

		public static void ToggleBlocklistOnHover(Player player)
		{
			Smelter val = HoveredOreSwitchSmelter(player);
			if (Object.op_Implicit((Object)(object)val))
			{
				Material material = Selected(val, Materials(val));
				if (material != null)
				{
					Settings.ToggleBlocklisted(material.Prefab);
					((Character)player).Message((MessageType)1, material.Name + ": " + (Blocklisted(material.Prefab) ? "never put into smelters automatically" : "allowed"), 0, (Sprite)null, false);
				}
			}
		}

		public static Smelter HoveredOreSwitchSmelter(Player player)
		{
			GameObject val = (Object.op_Implicit((Object)(object)player) ? ((Humanoid)player).GetHoverObject() : null);
			Switch val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponentInParent<Switch>() : null);
			Smelter val3 = (Object.op_Implicit((Object)(object)val2) ? ((Component)val2).GetComponentInParent<Smelter>() : null);
			if (!Object.op_Implicit((Object)(object)val3) || !((Object)(object)val3.m_addOreSwitch == (Object)(object)val2))
			{
				return null;
			}
			return val3;
		}

		public static void Prune()
		{
			foreach (Smelter item in s_pulls.Keys.Where((Smelter s) => !Object.op_Implicit((Object)(object)s)).ToList())
			{
				s_pulls.Remove(item);
			}
		}
	}
	[HarmonyPatch(typeof(Smelter), "OnHoverAddOre")]
	internal static class SmelterHoverOrePatch
	{
		private static void Postfix(Smelter __instance, ref string __result)
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Settings.Smelting.Value)
			{
				int queueSize = __instance.GetQueueSize();
				int num = SmelterInFlight.Queue(__instance);
				if (num != queueSize)
				{
					__result = ReplaceFirst(__result, $"({queueSize}/{__instance.m_maxOre})", $"({num}/{__instance.m_maxOre})");
				}
				if (Hooks.Active(Settings.Smelting.Value))
				{
					__result += SmeltFlow.HoverLines(__instance);
				}
			}
		}

		public static string ReplaceFirst(string text, string old, string replacement)
		{
			int num = text.IndexOf(old, StringComparison.Ordinal);
			if (num >= 0)
			{
				return text.Substring(0, num) + replacement + text.Substring(num + old.Length);
			}
			return text;
		}
	}
	[HarmonyPatch(typeof(Smelter), "OnHoverAddFuel")]
	internal static class SmelterHoverFuelPatch
	{
		private static void Postfix(Smelter __instance, ref string __result)
		{
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)__instance.m_fuelItem) || !Settings.Smelting.Value)
			{
				return;
			}
			float num = Mathf.Ceil(__instance.GetFuel());
			float num2 = Mathf.Ceil(SmelterInFlight.Fuel(__instance));
			if (num2 != num)
			{
				__result = SmelterHoverOrePatch.ReplaceFirst(__result, $" {num}/{__instance.m_maxFuel})", $" {num2}/{__instance.m_maxFuel})");
			}
			if (Hooks.Active(Settings.Smelting.Value))
			{
				string name = __instance.m_fuelItem.m_itemData.m_shared.m_name;
				if (SmeltFlow.InInventory(name) + StorageNetwork.Count(name, -1, matchWorldLevel: true, ownedOnly: true) == 0)
				{
					ChestAccess.BorrowFor(name);
				}
				__result += Localization.instance.Localize($"\n{SmeltFlow.InStorage(name)} {name} in storage");
			}
		}
	}
	[HarmonyPatch(typeof(Smelter), "OnAddOre")]
	internal static class SmelterAddOrePatch
	{
		private static bool Prefix(Smelter __instance, Humanoid user, ref ItemData item, ref bool __result)
		{
			return SmeltFlow.BeforeAddOre(__instance, user, ref item, ref __result);
		}

		private static void Postfix(Smelter __instance, Humanoid user, bool __result)
		{
			if (__result && (Object)(object)user == (Object)(object)Player.m_localPlayer)
			{
				SmelterInFlight.OreSent(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Smelter), "OnAddFuel")]
	internal static class SmelterAddFuelPatch
	{
		private static bool Prefix(Smelter __instance, Humanoid user, ItemData item, ref bool __result)
		{
			return SmeltFlow.BeforeAddFuel(__instance, user, item, ref __result);
		}

		private static void Postfix(Smelter __instance, Humanoid user, bool __result)
		{
			if (__result && (Object)(object)user == (Object)(object)Player.m_localPlayer)
			{
				SmelterInFlight.FuelSent(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Switch), "Interact")]
	internal static class SwitchInteractPatch
	{
		private static bool Prefix(Switch __instance, Humanoid character, bool hold, bool alt, ref bool __result)
		{
			if (!alt || !SmeltFlow.UsesAltUse || (Object)(object)character != (Object)(object)Player.m_localPlayer || !Hooks.Active(Settings.Smelting.Value))
			{
				return true;
			}
			Smelter componentInParent = ((Component)__instance).GetComponentInParent<Smelter>();
			if (!Object.op_Implicit((Object)(object)componentInParent) || (Object)(object)componentInParent.m_addOreSwitch != (Object)(object)__instance)
			{
				return true;
			}
			__result = false;
			if (!hold)
			{
				SmeltFlow.Cycle(componentInParent, character);
				__result = true;
			}
			return false;
		}
	}
	internal static class StorageLog
	{
		private static ManualLogSource s_log;

		private static string s_path;

		public static void Init(ManualLogSource log)
		{
			s_log = log;
			s_path = Path.Combine(Paths.BepInExRootPath, "CraftFromStorage.log");
		}

		public static void Info(string message)
		{
			ManualLogSource obj = s_log;
			if (obj != null)
			{
				obj.LogInfo((object)message);
			}
		}

		public static void Warning(string message)
		{
			ManualLogSource obj = s_log;
			if (obj != null)
			{
				obj.LogWarning((object)message);
			}
		}

		public static void Action(string message)
		{
			if (Settings.LogActions.Value)
			{
				Write(message);
			}
		}

		public static void Write(string message)
		{
			if (s_path == null)
			{
				return;
			}
			try
			{
				File.AppendAllText(s_path, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} {message}{Environment.NewLine}");
			}
			catch (Exception ex)
			{
				ManualLogSource obj = s_log;
				if (obj != null)
				{
					obj.LogWarning((object)("Couldn't write " + s_path + ": " + ex.Message));
				}
			}
		}
	}
	internal static class StorageNetwork
	{
		private const float RefreshInterval = 0.5f;

		private const float UnresponsiveSeconds = 30f;

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

		private static readonly HashSet<Inventory> s_inventories = new HashSet<Inventory>();

		private static readonly Dictionary<Container, bool> s_staticOk = new Dictionary<Container, bool>();

		private static readonly Dictionary<ZDOID, float> s_unresponsiveUntil = new Dictionary<ZDOID, float>();

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

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

		private static readonly List<(Container Chest, float Distance)> s_sortBuffer = new List<(Container, float)>();

		private static readonly Dictionary<(string Name, int Quality, bool MatchWorldLevel, bool OwnedOnly), int> s_counts = new Dictionary<(string, int, bool, bool), int>();

		private static int s_contentVersion;

		private static int s_countsVersion = -1;

		private static int s_settingsVersion = -1;

		private static int s_setSignature;

		private static float s_nextRefresh;

		private static SimulationDistance s_radiusFor;

		private static float s_radius;

		public static int SkippedNoOwner { get; private set; }

		public static int InUseByOthers { get; private set; }

		public static int Unresponsive { get; private set; }

		public static float EffectiveRange { get; private set; }

		public static bool RangeCapped { get; private set; }

		public static IReadOnlyList<Container> Candidates
		{
			get
			{
				Refresh();
				return s_candidates;
			}
		}

		public static IReadOnlyList<Container> Usable
		{
			get
			{
				Refresh();
				return s_usable;
			}
		}

		public static void Register(Container container)
		{
			if (Object.op_Implicit((Object)(object)container) && container.GetInventory() != null)
			{
				s_all.Add(container);
				s_inventories.Add(container.GetInventory());
			}
		}

		public static void OnInventoryChanged(Inventory inventory)
		{
			if (s_inventories.Contains(inventory))
			{
				s_contentVersion++;
			}
		}

		public static void Invalidate()
		{
			s_contentVersion++;
			s_nextRefresh = 0f;
		}

		public static void MarkUnresponsive(Container container)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)container) && Object.op_Implicit((Object)(object)container.m_nview) && container.m_nview.IsValid())
			{
				s_unresponsiveUntil[container.m_nview.GetZDO().m_uid] = Time.time + 30f;
				Invalidate();
			}
		}

		public static bool IsSinglePlayer()
		{
			if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer() && !ZNet.instance.IsDedicated())
			{
				return ZNet.instance.GetConnectedPeers().Count == 0;
			}
			return false;
		}

		public static bool IsOwnedForUse(Container container)
		{
			ZNetView nview = container.m_nview;
			if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
			{
				return false;
			}
			if (!nview.IsOwner())
			{
				if (nview.GetZDO().GetOwner() == 0L)
				{
					return IsSinglePlayer();
				}
				return false;
			}
			return true;
		}

		public static bool IsUsableItem(ItemData item)
		{
			return !EpicLootCompat.IsEnchanted(item);
		}

		public static bool Matches(ItemData item, string name, int quality, bool matchWorldLevel)
		{
			if (item.m_shared.m_name == name && (quality < 0 || item.m_quality == quality) && (!matchWorldLevel || item.m_worldLevel >= Game.m_worldLevel))
			{
				return IsUsableItem(item);
			}
			return false;
		}

		public static int Count(string name, int quality, bool matchWorldLevel, bool ownedOnly)
		{
			Refresh();
			if (s_countsVersion != s_contentVersion || s_settingsVersion != Settings.Version)
			{
				s_counts.Clear();
				s_countsVersion = s_contentVersion;
				s_settingsVersion = Settings.Version;
			}
			(string, int, bool, bool) key = (name, quality, matchWorldLevel, ownedOnly);
			if (s_counts.TryGetValue(key, out var value))
			{
				return value;
			}
			int num = 0;
			foreach (Container item in s_usable)
			{
				if (!ownedOnly || IsOwnedForUse(item))
				{
					num += CountIn(item.GetInventory(), name, quality, matchWorldLevel);
				}
			}
			s_counts[key] = num;
			return num;
		}

		public static int CountIn(Inventory inventory, string name, int quality, bool matchWorldLevel)
		{
			int num = 0;
			foreach (ItemData item in inventory.m_inventory)
			{
				if (Matches(item, name, quality, matchWorldLevel))
				{
					num += item.m_stack;
				}
			}
			return num;
		}

		public static ItemData FindItem(string name, int quality)
		{
			foreach (Container item in Usable)
			{
				foreach (ItemData item2 in item.GetInventory().m_inventory)
				{
					if (Matches(item2, name, quality, matchWorldLevel: true))
					{
						return item2;
					}
				}
			}
			return null;
		}

		private static void Refresh()
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < s_nextRefresh)
			{
				return;
			}
			s_nextRefresh = Time.time + 0.5f;
			s_candidates.Clear();
			s_usable.Clear();
			SkippedNoOwner = (InUseByOthers = (Unresponsive = 0));
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || !Object.op_Implicit((Object)(object)ZNet.instance))
			{
				s_setSignature = 0;
				return;
			}
			Prune();
			float num3 = GuaranteedLoadedRadius(ZNet.instance.GetSyncedSimulationDistance());
			RangeCapped = (float)Settings.Range.Value > num3;
			EffectiveRange = Mathf.Min((float)Settings.Range.Value, num3);
			Vector3 position = ((Component)localPlayer).transform.position;
			long playerID = localPlayer.GetPlayerID();
			bool flag = IsSinglePlayer();
			s_sortBuffer.Clear();
			foreach (Container item2 in s_all)
			{
				if (!((Behaviour)item2).isActiveAndEnabled || !IsStaticallyOk(item2) || !IsAllowedKind(item2))
				{
					continue;
				}
				ZNetView nview = item2.m_nview;
				if (!Object.op_Implicit((Object)(object)nview) || !nview.IsValid())
				{
					continue;
				}
				float num4 = Vector3.Distance(position, ((Component)item2).transform.position);
				if (num4 > EffectiveRange || !item2.CheckAccess(playerID) || (item2.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)item2).transform.position, 0f, false, false)))
				{
					continue;
				}
				ZDO zDO = nview.GetZDO();
				if (zDO.GetBool("MUC_Ignore", false))
				{
					continue;
				}
				if (zDO.GetOwner() == 0L && !flag)
				{
					SkippedNoOwner++;
					continue;
				}
				if (s_unresponsiveUntil.TryGetValue(zDO.m_uid, out var value))
				{
					if (Time.time < value)
					{
						Unresponsive++;
						continue;
					}
					s_unresponsiveUntil.Remove(zDO.m_uid);
				}
				s_sortBuffer.Add((item2, num4));
			}
			s_sortBuffer.Sort(((Container Chest, float Distance) a, (Container Chest, float Distance) b) => a.Distance.CompareTo(b.Distance));
			int num5 = 17;
			foreach (var item3 in s_sortBuffer)
			{
				Container item = item3.Chest;
				s_candidates.Add(item);
				bool flag2 = IsOwnedForUse(item);
				if (!flag2 && item.m_nview.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 1)
				{
					InUseByOthers++;
				}
				if (flag2 || MucHealth.CanUseNonOwnedChests)
				{
					s_usable.Add(item);
					num5 = num5 * 31 + ((Object)item).GetInstanceID() * (flag2 ? 3 : 7);
				}
			}
			if (num5 != s_setSignature)
			{
				s_setSignature = num5;
				s_contentVersion++;
			}
		}

		public static float GuaranteedLoadedRadius(SimulationDistance distance)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			if (((SimulationDistance)(ref distance)).Equals(s_radiusFor) && s_radius > 0f)
			{
				return s_radius;
			}
			int num = Mathf.Max(0, ((SimulationDistance)(ref distance)).NearSimulationDistance);
			float num2 = 64f;
			float num3 = (float)(num + 1) * num2;
			for (int i = -(num + 1); i <= num + 1; i++)
			{
				for (int j = -(num + 1); j <= num + 1; j++)
				{
					bool num4 = Mathf.Abs(i) > num || Mathf.Abs(j) > num;
					int num5;
					if (!((SimulationDistance)(ref distance)).IsClassic)
					{
						Vector2 val = new Vector2((float)i, (float)j);
						num5 = ((((Vector2)(ref val)).magnitude * num2 >= ((float)num + 0.5f) * num2) ? 1 : 0);
					}
					else
					{
						num5 = 0;
					}
					bool flag = (byte)num5 != 0;
					if (num4 || flag)
					{
						float num6 = (float)Mathf.Max(0, Mathf.Abs(i) - 1) * num2;
						float num7 = (float)Mathf.Max(0, Mathf.Abs(j) - 1) * num2;
						num3 = Mathf.Min(num3, Mathf.Sqrt(num6 * num6 + num7 * num7));
					}
				}
			}
			s_radiusFor = distance;
			s_radius = Mathf.Max(num2, num3);
			return s_radius;
		}

		public static void Prune()
		{
			if (s_all.RemoveAll((Container c) => !Object.op_Implicit((Object)(object)c)) == 0)
			{
				return;
			}
			s_inventories.Clear();
			foreach (Container item in s_all)
			{
				s_inventories.Add(item.GetInventory());
			}
			List<Container> list = new List<Container>();
			foreach (Container key in s_staticOk.Keys)
			{
				if (!Object.op_Implicit((Object)(object)key))
				{
					list.Add(key);
				}
			}
			foreach (Container item2 in list)
			{
				s_staticOk.Remove(item2);
			}
		}

		private static bool IsStaticallyOk(Container chest)
		{
			if (s_staticOk.TryGetValue(chest, out var value))
			{
				return value;
			}
			Piece componentInParent = ((Component)chest).GetComponentInParent<Piece>();
			value = !Object.op_Implicit((Object)(object)((Component)chest).GetComponentInParent<Player>()) && !Object.op_Implicit((Object)(object)((Component)chest).GetComponentInParent<TombStone>()) && Object.op_Implicit((Object)(object)componentInParent) && componentInParent.IsPlacedByPlayer();
			s_staticOk[chest] = value;
			return value;
		}

		private static bool IsAllowedKind(Container chest)
		{
			if (!Settings.CartsAndShips.Value && (Object.op_Implicit((Object)(object)chest.m_wagon) || Object.op_Implicit((Object)(object)((Component)chest).GetComponentInParent<Ship>())))
			{
				return false;
			}
			if (Settings.Excluded.Count != 0)
			{
				return !Settings.Excluded.Contains(PrefabName(Object.op_Implicit((Object)(object)chest.m_nview) ? ((Component)chest.m_nview).gameObject : ((Component)chest).gameObject));
			}
			return true;
		}

		public static string PrefabName(GameObject go)
		{
			return ((Object)go).name.Replace("(Clone)", "").Trim();
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "s992.CraftFromStorage";

		public const string PLUGIN_NAME = "CraftFromStorage";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}