Decompiled source of ReliableCrafting v1.0.0

BepInEx/plugins/ReliableCrafting/ReliableCrafting.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using ReliableStationSafety;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: AssemblyMetadata("AI_Assisted_Creation", "Coded with the assistance of ChatGPT 6.0; packaged with OpenAI Codex.")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyMetadata("AI_Model_Vendor", "OpenAI")]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace ReliableCrafting
{
	[BepInPlugin("au.tyvalheim.reliablecrafting", "Reliable Crafting", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class PendingCraft
		{
			internal InventoryGui Gui;

			internal Player Player;

			internal CraftingStation Station;

			internal Vector3 Position;

			internal object[] Values;

			internal int UpgradeQuality;

			internal List<Container> Sources;

			internal CraftWait Wait;
		}

		private sealed class Transfer
		{
			internal Entry Source;

			internal ItemData Item;

			internal ItemData Copy;

			internal int Amount;
		}

		public const string Id = "au.tyvalheim.reliablecrafting";

		private List<Container> waitingSources;

		private PendingCraft pendingCraft;

		private bool resumingCraft;

		internal bool AutomaticCraftContext;

		private static readonly FieldInfo CraftTimer = AccessTools.Field(typeof(InventoryGui), "m_craftTimer");

		private static readonly MethodInfo FinishCraft = AccessTools.Method(typeof(InventoryGui), "DoCrafting", (Type[])null, (Type[])null);

		private static readonly string[] CraftFields = new string[6] { "m_craftRecipe", "m_craftUpgradeItem", "m_craftVariant", "m_multiCrafting", "m_multiCraftAmount", "m_selectedVariant" };

		private static readonly FieldInfo SelectedRecipe = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe");

		private string diagnosticRecipe = "none";

		private float nextCraftSnapshot;

		private string lastCraftSnapshot;

		internal static Plugin Instance;

		internal Storage Storage;

		internal ConfigEntry<float> Range;

		internal ConfigEntry<float> Refresh;

		internal ConfigEntry<float> Budget;

		internal ConfigEntry<bool> Enabled;

		internal ConfigEntry<bool> Cooking;

		internal ConfigEntry<bool> FoodFromChests;

		internal ConfigEntry<bool> Diagnostics;

		internal ConfigEntry<bool> Wardrobes;

		internal ConfigEntry<bool> Carts;

		internal ConfigEntry<bool> Boats;

		private Harmony harmony;

		private Player player;

		private ZNetScene scene;

		private bool faulted;

		private bool staging;

		internal bool NativeCraft;

		internal int RecipeListDepth;

		private float nextMessage;

		private float nextDiagnostics;

		private bool rowRefreshFaulted;

		internal bool HasPendingCraft => pendingCraft != null;

		private static object[] ReadCraft(InventoryGui gui)
		{
			object[] array = new object[CraftFields.Length];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = AccessTools.Field(typeof(InventoryGui), CraftFields[i]).GetValue(gui);
			}
			return array;
		}

		internal void WaitForCraft(InventoryGui gui, Player local)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			if (waitingSources != null && pendingCraft == null)
			{
				if (resumingCraft)
				{
					Tell("Craft cancelled: required chest access changed. Please try again.");
					return;
				}
				object[] array = ReadCraft(gui);
				object obj = array[1];
				ItemData val = (ItemData)((obj is ItemData) ? obj : null);
				pendingCraft = new PendingCraft
				{
					Gui = gui,
					Player = local,
					Station = local.GetCurrentCraftingStation(),
					Position = ((Component)local).transform.position,
					Values = array,
					UpgradeQuality = (val?.m_quality ?? 0),
					Sources = waitingSources,
					Wait = new CraftWait(Time.unscaledTime)
				};
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[Craft] Waiting automatically for chest access; recipe=" + ((Object)(Recipe)array[0]).name));
			}
		}

		internal void CancelPending(string reason)
		{
			PendingCraft pendingCraft = this.pendingCraft;
			if (pendingCraft != null)
			{
				this.pendingCraft = null;
				pendingCraft.Wait.Cancel();
				if ((Object)(object)pendingCraft.Gui != (Object)null)
				{
					CraftTimer.SetValue(pendingCraft.Gui, -1f);
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[Craft] Pending craft cancelled: " + reason));
			}
		}

		private bool SameCraft(PendingCraft pending)
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			if (!Active(pending.Player) || (Object)(object)pending.Player != (Object)(object)Player.m_localPlayer || (Object)(object)pending.Gui == (Object)null || (Object)(object)pending.Gui != (Object)(object)InventoryGui.instance || !InventoryGui.IsVisible())
			{
				return false;
			}
			if (!((Object)(object)pending.Player.GetCurrentCraftingStation() != (Object)(object)pending.Station))
			{
				Vector3 val = ((Component)pending.Player).transform.position - pending.Position;
				if (!(((Vector3)(ref val)).sqrMagnitude > 4f))
				{
					if (!object.ReferenceEquals(pending.Station, null) && (Object)(object)pending.Station == (Object)null)
					{
						return false;
					}
					if ((Object)(object)pending.Station != (Object)null && (!pending.Station.InUseDistance((Humanoid)(object)pending.Player) || !pending.Station.CheckUsable(pending.Player, false)))
					{
						return false;
					}
					object value = SelectedRecipe.GetValue(pending.Gui);
					if (!object.Equals(AccessTools.Property(value.GetType(), "Recipe").GetValue(value, null), pending.Values[0]) || !object.Equals(AccessTools.Property(value.GetType(), "ItemData").GetValue(value, null), pending.Values[1]))
					{
						return false;
					}
					object[] array = ReadCraft(pending.Gui);
					for (int i = 0; i < array.Length; i++)
					{
						if (!object.Equals(array[i], pending.Values[i]))
						{
							return false;
						}
					}
					object obj = array[1];
					ItemData val2 = (ItemData)((obj is ItemData) ? obj : null);
					if (val2 != null)
					{
						if (val2.m_quality == pending.UpgradeQuality)
						{
							return ((Humanoid)pending.Player).GetInventory().ContainsItem(val2);
						}
						return false;
					}
					return true;
				}
			}
			return false;
		}

		internal void ValidatePending()
		{
			if (pendingCraft != null && !SameCraft(pendingCraft))
			{
				CancelPending("recipe, player or workstation changed");
			}
		}

		internal void KeepPendingProgress(InventoryGui gui)
		{
			if (pendingCraft != null && (Object)(object)pendingCraft.Gui == (Object)(object)gui)
			{
				CraftTimer.SetValue(gui, 0f);
			}
		}

		private void TickPending()
		{
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			PendingCraft pendingCraft = this.pendingCraft;
			if (pendingCraft == null)
			{
				return;
			}
			if (!SameCraft(pendingCraft))
			{
				CancelPending("recipe, player or workstation changed");
			}
			else if (!Storage.CanAwait(pendingCraft.Sources))
			{
				CancelPending("access refused, expired or source unavailable");
				Tell("Craft cancelled: chest access is unavailable. No ingredients were pulled for the waiting craft.");
			}
			else if (pendingCraft.Wait.Expired(Time.unscaledTime))
			{
				CancelPending("access timeout");
				Tell("Craft cancelled: chest access timed out. Please try again.");
			}
			else if (pendingCraft.Wait.TryResume(Time.unscaledTime, sameContext: true, Storage.AwaitReady(pendingCraft.Sources)))
			{
				this.pendingCraft = null;
				CraftTimer.SetValue(pendingCraft.Gui, -1f);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[Craft] Access ready; resuming original craft once; recipe=" + ((Object)(Recipe)pendingCraft.Values[0]).name));
				resumingCraft = true;
				try
				{
					FinishCraft.Invoke(pendingCraft.Gui, new object[1] { pendingCraft.Player });
				}
				finally
				{
					resumingCraft = false;
				}
			}
		}

		internal void CraftLog(string phase, Recipe recipe, Player local, List<Need> needs)
		{
			if (Diagnostics == null || !Diagnostics.Value)
			{
				return;
			}
			try
			{
				if ((Object)(object)recipe != (Object)null)
				{
					diagnosticRecipe = ((Object)recipe).name;
				}
				string text = "[Craft diagnostic] t=" + Time.unscaledTime.ToString("F2") + "; recipe=" + diagnosticRecipe + "; phase=" + phase;
				if ((Object)(object)local != (Object)null)
				{
					CraftingStation currentCraftingStation = local.GetCurrentCraftingStation();
					Inventory inventory = ((Humanoid)local).GetInventory();
					object obj = text;
					text = string.Concat(obj, "; station=", ((Object)(object)currentCraftingStation == (Object)null) ? "none" : ((Object)currentCraftingStation).name, "; inventoryEntries=", inventory.GetAllItems().Count, "; grid=", inventory.GetWidth(), "x", inventory.GetHeight());
					if (needs != null)
					{
						foreach (Need need in needs)
						{
							object obj2 = text;
							text = string.Concat(obj2, "; need=", need.Name, "/q", need.Quality, ":", need.Amount, "/carried=", inventory.CountItems(need.Name, need.Quality, true));
						}
					}
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)text);
			}
			catch
			{
			}
		}

		internal void CraftUiSnapshot(InventoryGui gui, bool clicked = false)
		{
			if (Diagnostics == null || !Diagnostics.Value || (!clicked && Time.unscaledTime < nextCraftSnapshot))
			{
				return;
			}
			nextCraftSnapshot = Time.unscaledTime + 2f;
			try
			{
				object value = SelectedRecipe.GetValue(gui);
				if (value == null)
				{
					return;
				}
				object? value2 = AccessTools.Property(value.GetType(), "Recipe").GetValue(value, null);
				Recipe val = (Recipe)((value2 is Recipe) ? value2 : null);
				if (!((Object)(object)val == (Object)null))
				{
					object? value3 = AccessTools.Field(typeof(InventoryGui), "m_craftButton").GetValue(gui);
					Button val2 = (Button)((value3 is Button) ? value3 : null);
					string text = ((Object)val).name + "; buttonInteractable=" + ((Object)(object)val2 != (Object)null && ((Selectable)val2).interactable) + "; pending=" + HasPendingCraft + "; native=" + NativeCraft + "; faulted=" + faulted;
					if (clicked || !(text == lastCraftSnapshot))
					{
						lastCraftSnapshot = text;
						CraftLog((clicked ? "Craft click received; " : "UI ") + text, val, Player.m_localPlayer, null);
					}
				}
			}
			catch
			{
			}
		}

		private void Awake()
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Expected O, but got Unknown
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Expected O, but got Unknown
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: Expected O, but got Unknown
			Instance = this;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable nearby-container crafting assistance.");
			Range = ((BaseUnityPlugin)this).Config.Bind<float>("General", "RangeMeters", 30f, new ConfigDescription("3D range around the player. Loaded, permitted player-built storage only; measured at the container, including parked vehicle storage.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), new object[0]));
			Wardrobes = ((BaseUnityPlugin)this).Config.Bind<bool>("Storage", "UseWardrobes", true, "Use nearby player-built wardrobes as ingredient sources. Ward, privacy and busy-container checks still apply.");
			Carts = ((BaseUnityPlugin)this).Config.Bind<bool>("Storage", "UseCarts", true, "Use nearby player-built cart storage while parked. The cart must already be owned by this client and closed; crafting never requests or forces vehicle ownership. Open and close it normally before retrying if needed.");
			Boats = ((BaseUnityPlugin)this).Config.Bind<bool>("Storage", "UseBoats", true, "Use nearby player-built Karve, Longship and Drakkar storage while parked (normal vertical bobbing/rocking is allowed). The boat must already be owned by this client and its storage closed; crafting never requests or forces vehicle ownership. Rafts have no storage.");
			Cooking = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "CookingEnabled", true, "Supply manual cooking/fuel inputs, including Frost Foundry casts and Frigid Kiln ice. Assisted inputs and fuel require a locally owned station. Carried food in remote-owned ovens/racks uses native multiplayer input, paced by observed station progress. Cauldron recipes use normal crafting assistance.");
			FoodFromChests = ((BaseUnityPlugin)this).Config.Bind<bool>("Cooking", "FoodFromChests", false, "Opt in to pulling food from nearby chests when interacting with cooking stations or ovens. Default false: only carried food can be inserted, so stored meat and ingredients are not selected for cooking. Does not disable chest fuel, Frost Foundry casts, or cauldron/workbench recipe crafting. Requires General.CookingEnabled.");
			Refresh = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "RefreshIntervalSeconds", 0.5f, new ConfigDescription("Recheck active nearby containers; local inventory changes invalidate their summaries immediately.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), new object[0]));
			Budget = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "RefreshBudgetMilliseconds", 0.5f, new ConfigDescription("Soft per-frame refresh budget. One container refresh cannot be split; large containers can exceed this.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), new object[0]));
			Diagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Enabled", false, "Log recipe/button state changes, craft requirements and outcomes, source refusals, chest requests/replies and 30-second counters. Displayed assistance messages are always logged.");
			Storage = new Storage(this);
			Range.SettingChanged += delegate
			{
				Storage.Invalidate();
			};
			Wardrobes.SettingChanged += StorageSettingsChanged;
			Carts.SettingChanged += StorageSettingsChanged;
			Boats.SettingChanged += StorageSettingsChanged;
			try
			{
				if (Storage.Load == null || Storage.Access == null || Storage.Changed == null)
				{
					throw new MissingMemberException("Required game API is missing.");
				}
				harmony = new Harmony("au.tyvalheim.reliablecrafting");
				harmony.PatchAll(typeof(Plugin).Assembly);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Reliable Crafting 1.0.0 loaded. Food from chests=" + FoodFromChests.Value + "; wardrobes=" + Wardrobes.Value + "; carts=" + Carts.Value + "; boats=" + Boats.Value + "; assisted inputs, fuel and vehicle storage require local ownership; remote carried food follows native input with pending-input and capacity checks."));
			}
			catch (Exception ex)
			{
				Fault(ex);
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
		}

		private void StorageSettingsChanged(object sender, EventArgs args)
		{
			CancelPending("storage source setting changed");
			if (Storage != null)
			{
				Storage.Invalidate();
			}
		}

		internal bool Active(Player candidate)
		{
			if (!faulted && Enabled.Value && (Object)(object)candidate != (Object)null && (Object)(object)candidate == (Object)(object)Player.m_localPlayer)
			{
				return !((Character)candidate).IsDead();
			}
			return false;
		}

		internal bool RefuseFaultedInput(Player candidate)
		{
			if (!faulted || !Enabled.Value || !Cooking.Value || (Object)(object)candidate == (Object)null || (Object)(object)candidate != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			Tell("Station inputs paused after an error. No input consumed; restart Valheim before using assisted stations.");
			return true;
		}

		private void Update()
		{
			if (faulted)
			{
				return;
			}
			try
			{
				if ((Object)(object)scene != (Object)(object)ZNetScene.instance)
				{
					CancelPending("world changed");
					Storage.Dispose();
					Storage = new Storage(this);
					scene = ZNetScene.instance;
					if ((Object)(object)scene != (Object)null)
					{
						Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
						foreach (Container chest in array)
						{
							Storage.Add(chest);
						}
					}
				}
				if ((Object)(object)player != (Object)(object)Player.m_localPlayer)
				{
					player = Player.m_localPlayer;
					Storage.Invalidate();
				}
				TickPending();
				if (Active(player))
				{
					if (InventoryGui.IsVisible())
					{
						Storage.Nearby(player);
					}
					Storage.Tick(player);
					if (Diagnostics.Value && Time.unscaledTime >= nextDiagnostics)
					{
						nextDiagnostics = Time.unscaledTime + 30f;
						((BaseUnityPlugin)this).Logger.LogInfo((object)("refreshes=" + Storage.Refreshes + " inventoryRebuilds=" + Storage.InventoryRebuilds + " accessRequests=" + Storage.Requests));
					}
				}
			}
			catch (Exception ex)
			{
				Fault(ex);
			}
		}

		internal int Count(Inventory inventory, string name, int quality, bool worldLevel)
		{
			int num = inventory.CountItems(name, quality, worldLevel);
			Player localPlayer = Player.m_localPlayer;
			if (!Active(localPlayer) || (NativeCraft && RecipeListDepth == 0) || inventory != ((Humanoid)localPlayer).GetInventory())
			{
				return num;
			}
			try
			{
				return checked(num + Storage.Count(localPlayer, name, quality));
			}
			catch (Exception ex)
			{
				Fault(ex);
				return num;
			}
		}

		internal ItemData Example(Inventory inventory, string name, int quality, bool equipped)
		{
			ItemData item = inventory.GetItem(name, quality, equipped);
			Player localPlayer = Player.m_localPlayer;
			if (item != null || (NativeCraft && RecipeListDepth == 0) || !Active(localPlayer) || inventory != ((Humanoid)localPlayer).GetInventory())
			{
				return item;
			}
			try
			{
				return Storage.Example(localPlayer, name, quality);
			}
			catch (Exception ex)
			{
				Fault(ex);
				return item;
			}
		}

		internal List<Need> RecipeNeeds(Player local, Recipe recipe, int quality, int multiplier)
		{
			List<Need> list = new List<Need>();
			CraftingStation currentCraftingStation = local.GetCurrentCraftingStation();
			Requirement[] resources = recipe.m_resources;
			foreach (Requirement val in resources)
			{
				if ((Object)(object)val.m_resItem == (Object)null || (((Object)(object)currentCraftingStation != (Object)null) ? (val.m_upgraderResource != currentCraftingStation.m_upgrader) : val.m_upgraderResource))
				{
					continue;
				}
				int num = checked(val.GetAmount(quality) * multiplier);
				if (num <= 0)
				{
					continue;
				}
				string name = val.m_resItem.m_itemData.m_shared.m_name;
				if (recipe.m_requireOnlyOneIngredient)
				{
					for (int j = 0; j <= val.m_resItem.m_itemData.m_shared.m_maxQuality; j++)
					{
						if (Count(((Humanoid)local).GetInventory(), name, j, worldLevel: true) >= num)
						{
							list.Add(new Need
							{
								Name = name,
								Amount = num,
								Quality = j
							});
							return list;
						}
					}
				}
				else
				{
					list.Add(new Need
					{
						Name = name,
						Amount = num
					});
				}
			}
			if (recipe.m_requireOnlyOneIngredient)
			{
				return null;
			}
			return Policy.Combine(list);
		}

		internal bool Prepare(Player local, List<Need> needs)
		{
			//IL_03c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0400: Unknown result type (might be due to invalid IL or missing references)
			//IL_040b: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c8: Unknown result type (might be due to invalid IL or missing references)
			waitingSources = null;
			if (needs == null)
			{
				Tell("Required ingredients are unavailable.");
				return false;
			}
			if (staging)
			{
				Tell("An inventory transfer is already in progress.");
				return false;
			}
			List<Need> list = new List<Need>();
			foreach (Need item2 in Policy.Combine(needs))
			{
				int num = Math.Max(0, item2.Amount - ((Humanoid)local).GetInventory().CountItems(item2.Name, item2.Quality, true));
				if (num > 0)
				{
					list.Add(new Need
					{
						Name = item2.Name,
						Amount = num,
						Quality = item2.Quality
					});
				}
			}
			if (list.Count == 0)
			{
				return true;
			}
			List<Transfer> list2 = new List<Transfer>();
			Dictionary<ItemData, int> dictionary = new Dictionary<ItemData, int>();
			List<Entry> list3 = Storage.Nearby(local);
			foreach (Entry item3 in list3)
			{
				Storage.Refresh(item3, local);
			}
			foreach (Need item4 in list)
			{
				int num2 = item4.Amount;
				foreach (Entry item5 in list3)
				{
					if (!Storage.Allowed(item5.Chest, local, Range.Value, requireOwner: false))
					{
						continue;
					}
					foreach (ItemData allItem in item5.Inventory.GetAllItems())
					{
						if (allItem.m_stack > 0 && !(allItem.m_shared.m_name != item4.Name) && allItem.m_worldLevel >= Game.m_worldLevel && (item4.Quality < 0 || allItem.m_quality == item4.Quality))
						{
							dictionary.TryGetValue(allItem, out var value);
							int num3 = Math.Min(num2, Math.Max(0, allItem.m_stack - value));
							if (num3 > 0)
							{
								list2.Add(new Transfer
								{
									Source = item5,
									Item = allItem,
									Amount = num3
								});
								dictionary[allItem] = checked(value + num3);
							}
							num2 -= num3;
							if (num2 == 0)
							{
								break;
							}
						}
					}
					if (num2 == 0)
					{
						break;
					}
				}
				if (num2 > 0)
				{
					Tell("Materials unavailable: chests may be busy, ward-protected or out of range. Nothing pulled.");
					return false;
				}
			}
			HashSet<Container> hashSet = new HashSet<Container>();
			List<Container> list4 = new List<Container>();
			foreach (Transfer item6 in list2)
			{
				if (hashSet.Add(item6.Source.Chest))
				{
					list4.Add(item6.Source.Chest);
				}
			}
			if (!Policy.AllReady(list4, (Container chest) => Storage.Ready(chest, local, Range.Value)))
			{
				if (Storage.CanAwait(list4))
				{
					waitingSources = list4;
				}
				return false;
			}
			Inventory inventory = ((Humanoid)local).GetInventory();
			List<Vector2i> list5 = new List<Vector2i>();
			HashSet<Vector2i> hashSet2 = new HashSet<Vector2i>();
			foreach (ItemData allItem2 in inventory.GetAllItems())
			{
				hashSet2.Add(allItem2.m_gridPos);
			}
			Vector2i item = default(Vector2i);
			for (int num4 = 0; num4 < inventory.GetHeight(); num4++)
			{
				for (int num5 = 0; num5 < inventory.GetWidth(); num5++)
				{
					((Vector2i)(ref item))..ctor(num5, num4);
					if (!hashSet2.Contains(item))
					{
						list5.Add(item);
					}
				}
			}
			int num6 = 0;
			List<Transfer> list6 = new List<Transfer>();
			foreach (Transfer item7 in list2)
			{
				int num7 = item7.Amount;
				while (num7 > 0)
				{
					if (num6 == list5.Count)
					{
						Tell("Make room for crafting ingredients in your inventory. Nothing pulled.");
						return false;
					}
					int num8 = Math.Min(num7, Math.Max(1, item7.Item.m_shared.m_maxStackSize));
					ItemData val = item7.Item.Clone();
					val.m_stack = num8;
					val.m_gridPos = list5[num6++];
					val.m_equipped = false;
					list6.Add(new Transfer
					{
						Source = item7.Source,
						Item = item7.Item,
						Copy = val,
						Amount = num8
					});
					num7 -= num8;
				}
			}
			Dictionary<IList<ItemData>, Inventory> inventories = new Dictionary<IList<ItemData>, Inventory>();
			inventories[inventory.GetAllItems()] = inventory;
			foreach (Transfer item8 in list2)
			{
				if (!Storage.Allowed(item8.Source.Chest, local, Range.Value, requireOwner: true) || !item8.Source.Inventory.ContainsItem(item8.Item) || item8.Item.m_stack < item8.Amount)
				{
					Tell("Chest access or stock changed. Nothing pulled; retry.");
					return false;
				}
				inventories[item8.Source.Inventory.GetAllItems()] = item8.Source.Inventory;
			}
			List<Move<ItemData>> list7 = new List<Move<ItemData>>();
			foreach (Transfer item9 in list6)
			{
				list7.Add(new Move<ItemData>
				{
					Source = item9.Source.Inventory.GetAllItems(),
					Destination = inventory.GetAllItems(),
					Item = item9.Item,
					Copy = item9.Copy,
					Amount = item9.Amount
				});
			}
			staging = true;
			try
			{
				TransferBatch.Execute(list7, (ItemData i) => i.m_stack, delegate(ItemData i, int n)
				{
					i.m_stack = n;
				}, delegate(IList<ItemData> key)
				{
					Storage.Changed.Invoke(inventories[key], new object[2] { false, false });
				});
				return true;
			}
			finally
			{
				staging = false;
				Storage.Invalidate();
				HashSet<Entry> hashSet3 = new HashSet<Entry>();
				foreach (Transfer item10 in list2)
				{
					if (hashSet3.Add(item10.Source))
					{
						Storage.Refresh(item10.Source, local);
					}
				}
			}
		}

		public static bool RequestChestAccess(Container chest, Player local, float radius)
		{
			Plugin instance = Instance;
			if ((Object)(object)instance == (Object)null || !instance.Active(local))
			{
				return false;
			}
			try
			{
				return instance.Storage.Ready(chest, local, radius);
			}
			catch (Exception ex)
			{
				instance.Fault(ex);
				return false;
			}
		}

		internal void Tell(string message)
		{
			if (!((Object)(object)Player.m_localPlayer == (Object)null) && !(Time.unscaledTime < nextMessage))
			{
				nextMessage = Time.unscaledTime + 1.5f;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[Assistance] " + message));
				((Character)Player.m_localPlayer).Message((MessageType)1, "Reliable Crafting: " + message, 0, (Sprite)null, false);
			}
		}

		internal void AccessLog(string action, Container chest, long sender)
		{
			if (Diagnostics.Value)
			{
				ZNetView val = Storage.View(chest);
				ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null);
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[Chest access] " + action + "; chest=" + (((Object)(object)chest == (Object)null) ? "destroyed" : ((Object)chest).name) + "; id=" + ((val2 == null) ? "invalid" : ((object)Unsafe.As<ZDOID, ZDOID>(ref val2.m_uid)/*cast due to .constrained prefix*/).ToString()) + "; peer=" + sender + "; owner=" + ((val2 == null) ? 0 : val2.GetOwner()) + "; localOwner=" + ((Object)(object)val != (Object)null && val.IsValid() && val.IsOwner())));
			}
		}

		internal void Fault(Exception ex)
		{
			faulted = true;
			CancelPending("assistance fault");
			((BaseUnityPlugin)this).Logger.LogError((object)("Chest assistance and guarded station inputs paused until restart; ordinary recipe crafting and unrelated inventory actions remain available. " + ex));
		}

		private void OnDestroy()
		{
			CancelPending("plugin unloaded");
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if (Storage != null)
			{
				Storage.Dispose();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		internal void RefreshRecipeRows(InventoryGui gui, Player local)
		{
			if (rowRefreshFaulted || NativeCraft || !Active(local))
			{
				return;
			}
			try
			{
				RecipeRows.Refresh(gui, local, Storage);
			}
			catch (Exception ex)
			{
				rowRefreshFaulted = true;
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Live recipe-row display updates paused after a UI error. Native recipe selection and crafting remain available. " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")]
	internal static class PendingProgressPatch
	{
		private static void Prefix()
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.ValidatePending();
			}
		}

		private static void Postfix(InventoryGui __instance)
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.KeepPendingProgress(__instance);
				Plugin.Instance.CraftUiSnapshot(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")]
	internal static class PendingClickPatch
	{
		private static bool Prefix(InventoryGui __instance)
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.CraftUiSnapshot(__instance, clicked: true);
			}
			if (!((Object)(object)Plugin.Instance == (Object)null))
			{
				return !Plugin.Instance.HasPendingCraft;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "OnCraftCancelPressed")]
	internal static class PendingCancelPatch
	{
		private static void Prefix()
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.CancelPending("player cancelled");
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "Hide")]
	internal static class PendingHidePatch
	{
		private static void Prefix()
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.CancelPending("menu closed");
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "SetRecipe")]
	internal static class PendingSelectionPatch
	{
		private static void Postfix()
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.ValidatePending();
			}
		}
	}
	internal static class Hooks
	{
		internal static int Count(Inventory inv, string name, int quality, bool world)
		{
			if (!((Object)(object)Plugin.Instance == (Object)null))
			{
				return Plugin.Instance.Count(inv, name, quality, world);
			}
			return inv.CountItems(name, quality, world);
		}

		internal static ItemData Get(Inventory inv, string name, int quality, bool equipped)
		{
			if (!((Object)(object)Plugin.Instance == (Object)null))
			{
				return Plugin.Instance.Example(inv, name, quality, equipped);
			}
			return inv.GetItem(name, quality, equipped);
		}

		internal static int UiCount(Inventory inv, string name, int quality, bool world, bool craft)
		{
			if (!craft)
			{
				return inv.CountItems(name, quality, world);
			}
			return Count(inv, name, quality, world);
		}

		internal static IEnumerable<CodeInstruction> Replace(IEnumerable<CodeInstruction> instructions, bool ui, bool item)
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>();
			int num = 0;
			int num2 = 0;
			MethodInfo methodInfo = AccessTools.Method(typeof(Inventory), "CountItems", new Type[3]
			{
				typeof(string),
				typeof(int),
				typeof(bool)
			}, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(Inventory), "GetItem", new Type[3]
			{
				typeof(string),
				typeof(int),
				typeof(bool)
			}, (Type[])null);
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, methodInfo))
				{
					if (ui)
					{
						CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_3, (object)null);
						val.labels.AddRange(instruction.labels);
						instruction.labels.Clear();
						val.blocks.AddRange(instruction.blocks);
						instruction.blocks.Clear();
						list.Add(val);
					}
					instruction.opcode = OpCodes.Call;
					instruction.operand = AccessTools.Method(typeof(Hooks), ui ? "UiCount" : "Count", (Type[])null, (Type[])null);
					num++;
				}
				else if (item && CodeInstructionExtensions.Calls(instruction, methodInfo2))
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = AccessTools.Method(typeof(Hooks), "Get", (Type[])null, (Type[])null);
					num2++;
				}
				list.Add(instruction);
			}
			if (num != 1 || (item && num2 != 1))
			{
				throw new InvalidOperationException("Unsupported resource-check IL layout; assistance will not load.");
			}
			return list;
		}
	}
	[HarmonyPatch(typeof(Player), "HaveRequirementItems", new Type[]
	{
		typeof(Recipe),
		typeof(bool),
		typeof(int),
		typeof(int)
	})]
	internal static class RequirementPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return Hooks.Replace(instructions, ui: false, item: false);
		}
	}
	[HarmonyPatch(typeof(Player), "GetFirstRequiredItem")]
	internal static class AlternativeIngredientPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return Hooks.Replace(instructions, ui: false, item: true);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "SetupRequirement")]
	internal static class UiPatch
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return Hooks.Replace(instructions, ui: true, item: false);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList")]
	internal static class RecipeListPatch
	{
		private static void Prefix(out Plugin __state)
		{
			__state = Plugin.Instance;
			if ((Object)(object)__state != (Object)null)
			{
				__state.RecipeListDepth++;
			}
		}

		private static Exception Finalizer(Exception __exception, Plugin __state)
		{
			if ((Object)(object)__state != (Object)null)
			{
				__state.RecipeListDepth--;
			}
			return __exception;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	internal static class CraftPatch
	{
		[HarmonyPriority(800)]
		private static bool Prefix(InventoryGui __instance, Player player, Recipe ___m_craftRecipe, ItemData ___m_craftUpgradeItem, bool ___m_multiCrafting, int ___m_multiCraftAmount, out bool __state)
		{
			__state = false;
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.CraftLog("DoCrafting entered", ___m_craftRecipe, player, null);
			}
			if ((Object)(object)instance == (Object)null || !instance.Active(player) || (Object)(object)___m_craftRecipe == (Object)null)
			{
				return true;
			}
			if (instance.NativeCraft)
			{
				return false;
			}
			if (instance.HasPendingCraft)
			{
				return false;
			}
			try
			{
				if (!player.NoCostCheat() && !ZoneSystem.instance.GetGlobalKey((GlobalKeys)25))
				{
					int num = ((___m_craftUpgradeItem == null) ? 1 : (___m_craftUpgradeItem.m_quality + 1));
					List<Need> needs = instance.RecipeNeeds(player, ___m_craftRecipe, num, (!___m_multiCrafting) ? 1 : ___m_multiCraftAmount);
					instance.CraftLog("requirements; targetQuality=" + num, ___m_craftRecipe, player, needs);
					instance.AutomaticCraftContext = true;
					bool flag;
					try
					{
						flag = instance.Prepare(player, needs);
					}
					finally
					{
						instance.AutomaticCraftContext = false;
					}
					if (!flag)
					{
						instance.WaitForCraft(__instance, player);
						instance.CraftLog("preparation refused; pending=" + instance.HasPendingCraft, ___m_craftRecipe, player, null);
						return false;
					}
				}
				instance.NativeCraft = true;
				__state = true;
				instance.CraftLog("inputs ready; handing to native crafting", ___m_craftRecipe, player, null);
				return true;
			}
			catch (Exception ex)
			{
				instance.Fault(ex);
				return false;
			}
		}

		private static Exception Finalizer(Exception __exception, bool __state)
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.CraftLog("DoCrafting exited; handedToNative=" + __state + "; exception=" + ((__exception == null) ? "none" : __exception.ToString()), null, Player.m_localPlayer, null);
			}
			if (__state && (Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.NativeCraft = false;
			}
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class RegisterPatch
	{
		private static void Postfix(Container __instance)
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.Storage.Add(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Container), "RPC_OpenResponse")]
	internal static class ResponsePatch
	{
		private static bool Prefix(Container __instance, long __0, bool __1)
		{
			if (!((Object)(object)Plugin.Instance == (Object)null))
			{
				return Plugin.Instance.Storage.Response(__instance, __0, __1);
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Container), "Interact")]
	internal static class ManualOpenPatch
	{
		private static void Prefix(Container __instance, Humanoid __0, bool __1)
		{
			if (!__1 && !((Object)(object)__0 != (Object)(object)Player.m_localPlayer) && !((Object)(object)Plugin.Instance == (Object)null))
			{
				Plugin.Instance.Storage.ManualOpen(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(CookingStation), "OnInteract")]
	internal static class CookPatch
	{
		private static readonly MethodInfo Find = AccessTools.Method(typeof(CookingStation), "FindCookableItem", (Type[])null, (Type[])null);

		private static readonly MethodInfo Done = AccessTools.Method(typeof(CookingStation), "HaveDoneItem", (Type[])null, (Type[])null);

		private static readonly MethodInfo Free = AccessTools.Method(typeof(CookingStation), "GetFreeSlot", (Type[])null, (Type[])null);

		private static readonly MethodInfo Fire = AccessTools.Method(typeof(CookingStation), "IsFireLit", (Type[])null, (Type[])null);

		private static bool Prefix(CookingStation __instance, Humanoid __0, ZNetView ___m_nview, ref bool __result, bool __runOriginal)
		{
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			if (!Policy.SupportsCookingStation(((Object)__instance).name))
			{
				return true;
			}
			if (!__runOriginal)
			{
				return false;
			}
			Plugin self = Plugin.Instance;
			Player local = (Player)(object)((__0 is Player) ? __0 : null);
			if ((Object)(object)self == (Object)null || !self.Active(local) || !self.Cooking.Value)
			{
				return true;
			}
			if (!Policy.CanPullCookingInput(((Object)__instance).name, self.FoodFromChests.Value))
			{
				return true;
			}
			try
			{
				if ((bool)Done.Invoke(__instance, null) || Find.Invoke(__instance, new object[1] { ((Humanoid)local).GetInventory() }) != null)
				{
					return true;
				}
				if (!StationAccess.Ready((Component)(object)__instance, local))
				{
					__result = false;
					return false;
				}
				if ((int)Free.Invoke(__instance, null) < 0 || !PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, true) || (__instance.m_requireFire && !(bool)Fire.Invoke(__instance, null)))
				{
					__result = false;
					return false;
				}
				List<ItemConversion> list = __instance.m_conversion.Where((ItemConversion c) => c != null && (Object)(object)c.m_from != (Object)null && self.Storage.Count(local, c.m_from.m_itemData.m_shared.m_name, -1) > 0).ToList();
				if (__instance.m_recordCrafter && list.Select((ItemConversion c) => c.m_from.m_itemData.m_shared.m_name).Distinct().Count() > 1)
				{
					self.Tell("Several casts are available. Carry the cast you want to use; nothing moved.");
					__result = false;
					return false;
				}
				foreach (ItemConversion item in list)
				{
					string name = item.m_from.m_itemData.m_shared.m_name;
					if (self.Storage.Count(local, name, -1) <= 0)
					{
						continue;
					}
					if (self.Prepare(local, new List<Need>
					{
						new Need
						{
							Name = name,
							Amount = 1
						}
					}))
					{
						if (___m_nview.IsOwner())
						{
							return true;
						}
						self.Tell("Input moved into your inventory; station ownership changed. Nothing inserted.");
					}
					__result = false;
					return false;
				}
				self.Tell("No available station input; busy and ward-protected chests are excluded.");
				__result = false;
				return false;
			}
			catch (Exception ex)
			{
				self.Fault(ex);
				__result = false;
				return false;
			}
		}
	}
	[HarmonyPatch(typeof(CookingStation), "OnAddFuelSwitch")]
	internal static class OvenFuelPatch
	{
		private static bool Prefix(CookingStation __instance, Humanoid __1, ItemData __2, ZNetView ___m_nview, ref bool __result, bool __runOriginal)
		{
			if (!Policy.SupportsCookingStation(((Object)__instance).name))
			{
				return true;
			}
			if (!__runOriginal)
			{
				return false;
			}
			Plugin instance = Plugin.Instance;
			Player val = (Player)(object)((__1 is Player) ? __1 : null);
			if ((Object)(object)__instance.m_fuelItem != (Object)null && (Object)(object)instance != (Object)null && instance.RefuseFaultedInput(val))
			{
				__result = false;
				return false;
			}
			if ((Object)(object)instance == (Object)null || !instance.Active(val) || !instance.Cooking.Value || (Object)(object)__instance.m_fuelItem == (Object)null)
			{
				return true;
			}
			try
			{
				return StationTransfer.Fuel((Component)(object)__instance, val, __2, __instance.m_fuelItem, __instance.m_maxFuel, ref __result);
			}
			catch (Exception ex)
			{
				instance.Fault(ex);
				__result = false;
				return false;
			}
		}
	}
	internal enum StorageKind
	{
		Unsupported,
		Chest,
		Wardrobe,
		Cart,
		Boat
	}
	internal sealed class Need
	{
		internal string Name;

		internal int Amount;

		internal int Quality = -1;
	}
	internal static class Policy
	{
		internal static StorageKind StorageType(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return StorageKind.Unsupported;
			}
			if (name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				name = name.Substring(0, name.Length - "(Clone)".Length);
			}
			switch (name)
			{
			case "piece_chest_wood":
			case "piece_chest":
			case "piece_chest_blackmetal":
			case "piece_chest_private":
				return StorageKind.Chest;
			case "piece_chest_warderobe":
				return StorageKind.Wardrobe;
			case "Cart":
				return StorageKind.Cart;
			case "Karve":
			case "VikingShip":
			case "VikingShip_Ashlands":
				return StorageKind.Boat;
			default:
				return StorageKind.Unsupported;
			}
		}

		internal static bool Mobile(StorageKind kind)
		{
			if (kind != StorageKind.Cart)
			{
				return kind == StorageKind.Boat;
			}
			return true;
		}

		internal static bool Stationary(float speedSquared, float angularSpeedSquared)
		{
			if (speedSquared >= 0f && speedSquared <= 0.01f && angularSpeedSquared >= 0f)
			{
				return angularSpeedSquared <= 0.01f;
			}
			return false;
		}

		internal static bool StorageEnabled(StorageKind kind, bool wardrobes, bool carts, bool boats)
		{
			if (kind != StorageKind.Chest && (kind != StorageKind.Wardrobe || !wardrobes) && (kind != StorageKind.Cart || !carts))
			{
				if (kind == StorageKind.Boat)
				{
					return boats;
				}
				return false;
			}
			return true;
		}

		internal static bool SupportsCookingStation(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			if (name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				name = name.Substring(0, name.Length - "(Clone)".Length);
			}
			switch (name)
			{
			default:
				return name == "piece_FrostFoundry";
			case "piece_cookingstation":
			case "piece_cookingstation_iron":
			case "piece_oven":
				return true;
			}
		}

		internal static bool CanPullCookingInput(string name, bool foodFromChests)
		{
			if (SupportsCookingStation(name))
			{
				if (!foodFromChests && !(name == "piece_FrostFoundry"))
				{
					return name == "piece_FrostFoundry(Clone)";
				}
				return true;
			}
			return false;
		}

		internal static bool IsFoodStation(string name)
		{
			if (SupportsCookingStation(name) && name != "piece_FrostFoundry")
			{
				return name != "piece_FrostFoundry(Clone)";
			}
			return false;
		}

		internal static bool AllReady<T>(IEnumerable<T> sources, Func<T, bool> ready)
		{
			bool result = true;
			foreach (T source in sources)
			{
				if (!ready(source))
				{
					result = false;
				}
			}
			return result;
		}

		internal static bool InRange(float squaredDistance, float radius)
		{
			if (!float.IsNaN(radius) && radius >= 1f && radius <= 100f && squaredDistance >= 0f)
			{
				return squaredDistance <= radius * radius;
			}
			return false;
		}

		internal static int Cell(float coordinate)
		{
			return (int)Math.Floor(coordinate / 16f);
		}

		internal static List<Need> Combine(IEnumerable<Need> input)
		{
			List<Need> list = new List<Need>();
			checked
			{
				foreach (Need need in input)
				{
					if (need.Amount < 0)
					{
						throw new ArgumentOutOfRangeException("input");
					}
					if (need.Amount != 0)
					{
						if (string.IsNullOrEmpty(need.Name))
						{
							throw new ArgumentException("Missing material name");
						}
						Need need2 = list.Find((Need n) => n.Name == need.Name && n.Quality == need.Quality);
						if (need2 == null)
						{
							list.Add(new Need
							{
								Name = need.Name,
								Quality = need.Quality,
								Amount = need.Amount
							});
						}
						else
						{
							need2.Amount += need.Amount;
						}
					}
				}
				return list;
			}
		}
	}
	internal sealed class ActivityWindow
	{
		private float until = float.NegativeInfinity;

		internal void Touch(float now)
		{
			until = now + 1f;
		}

		internal bool IsActive(float now)
		{
			return now <= until;
		}
	}
	internal sealed class CraftWait
	{
		private readonly float started;

		private bool finished;

		internal CraftWait(float now)
		{
			started = now;
		}

		internal bool Expired(float now)
		{
			return now - started >= 5f;
		}

		internal void Cancel()
		{
			finished = true;
		}

		internal bool TryResume(float now, bool sameContext, bool allReady)
		{
			if (finished)
			{
				return false;
			}
			if (!sameContext || Expired(now))
			{
				finished = true;
				return false;
			}
			if (!allReady)
			{
				return false;
			}
			finished = true;
			return true;
		}
	}
	internal sealed class ChestAccessRoutes<T>
	{
		internal readonly Dictionary<T, OpenRequest> Requests = new Dictionary<T, OpenRequest>();

		private readonly HashSet<T> native = new HashSet<T>();

		private readonly HashSet<T> assisted = new HashSet<T>();

		internal bool WasAssisted(T chest)
		{
			return assisted.Contains(chest);
		}

		internal void BeginAssisted(T chest)
		{
			native.Remove(chest);
			assisted.Add(chest);
		}

		internal void CompleteManual(T chest)
		{
			native.Remove(chest);
		}

		internal bool IsNative(T chest)
		{
			return native.Contains(chest);
		}

		internal bool UseNative(T chest)
		{
			Requests.Remove(chest);
			return native.Add(chest);
		}

		internal void Remove(T chest)
		{
			Requests.Remove(chest);
			native.Remove(chest);
			assisted.Remove(chest);
		}

		internal void Clear()
		{
			Requests.Clear();
			native.Clear();
			assisted.Clear();
		}
	}
	internal sealed class OpenRequest
	{
		internal long Owner;

		internal float Started;

		internal bool Answered;

		internal bool Granted;

		internal float AnswerTime;

		private float ownedSince = float.NaN;

		internal bool Accept(long sender, bool granted, float now)
		{
			if (Answered || sender != Owner)
			{
				return false;
			}
			Answered = true;
			Granted = granted;
			AnswerTime = now;
			return true;
		}

		internal bool Ready(bool owned, float now)
		{
			if (!owned)
			{
				ownedSince = float.NaN;
				return false;
			}
			if (float.IsNaN(ownedSince))
			{
				ownedSince = now;
			}
			if (!TimedOut(now))
			{
				return now - ownedSince >= 0.25f;
			}
			return false;
		}

		internal bool TimedOut(float now)
		{
			return now - Started >= 5f;
		}
	}
	internal static class RecipeRows
	{
		private static readonly Type Pair = typeof(InventoryGui).GetNestedType("RecipeDataPair", BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly FieldInfo Rows = AccessTools.Field(typeof(InventoryGui), "m_availableRecipes");

		private static readonly FieldInfo Selected = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe");

		private static readonly PropertyInfo Recipe = AccessTools.Property(Pair, "Recipe");

		private static readonly PropertyInfo Item = AccessTools.Property(Pair, "ItemData");

		private static readonly PropertyInfo Element = AccessTools.Property(Pair, "InterfaceElement");

		private static readonly PropertyInfo Available = AccessTools.Property(Pair, "CanCraft");

		private static InventoryGui lastGui;

		private static Storage lastStorage;

		private static long lastRefreshes;

		private static float nextCheck;

		internal static void Refresh(InventoryGui gui, Player player, Storage storage)
		{
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Expected O, but got Unknown
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Expected O, but got Unknown
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Expected O, but got Unknown
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Expected O, but got Unknown
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			//IL_0271: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)gui == (Object)(object)lastGui && storage == lastStorage && storage.Refreshes == lastRefreshes && Time.unscaledTime < nextCheck)
			{
				return;
			}
			lastGui = gui;
			lastStorage = storage;
			lastRefreshes = storage.Refreshes;
			nextCheck = Time.unscaledTime + 0.5f;
			IList list = (IList)Rows.GetValue(gui);
			if (list == null || list.Count == 0)
			{
				return;
			}
			bool flag = (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)25);
			CraftingStation currentCraftingStation = player.GetCurrentCraftingStation();
			bool flag2 = (Object)(object)currentCraftingStation != (Object)null && currentCraftingStation.m_upgrader;
			object value = Selected.GetValue(gui);
			GameObject val = (GameObject)Element.GetValue(value, null);
			for (int i = 0; i < list.Count; i++)
			{
				object obj = list[i];
				Recipe val2 = (Recipe)Recipe.GetValue(obj, null);
				ItemData val3 = (ItemData)Item.GetValue(obj, null);
				GameObject val4 = (GameObject)Element.GetValue(obj, null);
				if ((Object)(object)val2 == (Object)null || (Object)(object)val4 == (Object)null)
				{
					continue;
				}
				bool flag3 = (val3 == null || val3.m_quality < val3.m_shared.m_maxQuality || flag2 || flag) && (flag || player.HaveRequirements(val2, false, (val3 == null) ? 1 : (val3.m_quality + 1), 1));
				if (flag3 != (bool)Available.GetValue(obj, null))
				{
					Available.SetValue(obj, flag3, null);
					list[i] = obj;
					if ((Object)(object)val4 == (Object)(object)val)
					{
						Selected.SetValue(gui, obj);
					}
					Transform val5 = val4.transform.Find("name");
					Transform val6 = val4.transform.Find("icon");
					TMP_Text val7 = (((Object)(object)val5 != (Object)null) ? ((Component)val5).GetComponent<TMP_Text>() : null);
					Image val8 = (((Object)(object)val6 != (Object)null) ? ((Component)val6).GetComponent<Image>() : null);
					if ((Object)(object)val7 != (Object)null)
					{
						((Graphic)val7).color = (Color)(flag3 ? Color.white : new Color(0.66f, 0.66f, 0.66f, 1f));
					}
					if ((Object)(object)val8 != (Object)null)
					{
						((Graphic)val8).color = (Color)(flag3 ? Color.white : new Color(1f, 0f, 1f, 0f));
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")]
	internal static class RecipeRowRefreshPatch
	{
		private static void Postfix(InventoryGui __instance, Player __0)
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.RefreshRecipeRows(__instance, __0);
			}
		}
	}
	internal static class StationAccess
	{
		internal static bool Ready(Component station, Player player)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			ZNetView component = station.GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner())
			{
				Plugin.Instance.Tell("Another peer controls this station, or it is still loading. Input kept in your inventory; retry when this client owns the station.");
				return false;
			}
			if (!PrivateArea.CheckAccess(station.transform.position, 0f, false, true))
			{
				Plugin.Instance.Tell("Station is ward-protected; nothing moved.");
				return false;
			}
			if ((Object)(object)player != (Object)null && !((Character)player).IsDead())
			{
				return !((Character)player).IsTeleporting();
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(CookingStation), "CookItem")]
	internal static class StationInputGuard
	{
		[HarmonyPriority(800)]
		private static bool Prefix(CookingStation __instance, Humanoid __0, ItemData __1, ref bool __result, bool __runOriginal, out FoodAttempt __state)
		{
			__state = null;
			if (!__runOriginal)
			{
				return false;
			}
			Plugin instance = Plugin.Instance;
			Player val = (Player)(object)((__0 is Player) ? __0 : null);
			if (Policy.SupportsCookingStation(((Object)__instance).name) && (Object)(object)instance != (Object)null && instance.RefuseFaultedInput(val))
			{
				__result = false;
				return false;
			}
			if ((Object)(object)instance == (Object)null || !instance.Active(val) || !instance.Cooking.Value || !Policy.SupportsCookingStation(((Object)__instance).name))
			{
				return true;
			}
			try
			{
				ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
				if (Policy.IsFoodStation(((Object)__instance).name) && (Object)(object)component != (Object)null && component.IsValid() && component.HasOwner() && !component.IsOwner())
				{
					string message;
					bool flag = RemoteFoodInput.Begin(__instance, val, __1, out __state, out message);
					if (message != null)
					{
						instance.Tell(message);
					}
					if (!flag)
					{
						__result = false;
					}
					return flag;
				}
				return StationTransfer.Input(__instance, val, __1, ref __result);
			}
			catch (Exception ex)
			{
				instance.Fault(ex);
				__result = false;
				return false;
			}
		}

		private static Exception Finalizer(Exception __exception, FoodAttempt __state)
		{
			RemoteFoodInput.Finish(__state);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Smelter), "OnAddFuel")]
	internal static class SmelterFuelPatch
	{
		private static bool Prefix(Smelter __instance, Humanoid __1, ItemData __2, ref bool __result, bool __runOriginal)
		{
			if (!__runOriginal)
			{
				return false;
			}
			Plugin instance = Plugin.Instance;
			Player val = (Player)(object)((__1 is Player) ? __1 : null);
			if ((Object)(object)__instance.m_fuelItem != (Object)null && __instance.m_maxFuel > 0 && (Object)(object)instance != (Object)null && instance.RefuseFaultedInput(val))
			{
				__result = false;
				return false;
			}
			if ((Object)(object)instance == (Object)null || !instance.Active(val) || !instance.Cooking.Value || (Object)(object)__instance.m_fuelItem == (Object)null || __instance.m_maxFuel <= 0)
			{
				return true;
			}
			try
			{
				return StationTransfer.Fuel((Component)(object)__instance, val, __2, __instance.m_fuelItem, __instance.m_maxFuel, ref __result);
			}
			catch (Exception ex)
			{
				instance.Fault(ex);
				__result = false;
				return false;
			}
		}
	}
	internal static class StationTransfer
	{
		internal static void Move(Inventory inventory, ItemData item, Func<bool> valid, Func<bool> committed, Action write)
		{
			if (!valid() || !inventory.ContainsItem(item) || item.m_stack <= 0)
			{
				throw new InvalidOperationException("Transfer eligibility changed before debit.");
			}
			List<ItemData> allItems = inventory.GetAllItems();
			int val = allItems.IndexOf(item);
			int stack = item.m_stack;
			item.m_stack--;
			if (item.m_stack == 0)
			{
				allItems.Remove(item);
			}
			try
			{
				if (!valid())
				{
					throw new InvalidOperationException("Ownership/access changed before station commit.");
				}
				write();
				if (!committed())
				{
					throw new InvalidOperationException("Station did not accept its input.");
				}
			}
			catch
			{
				if (!committed())
				{
					item.m_stack = stack;
					if (!allItems.Contains(item))
					{
						allItems.Insert(Math.Min(val, allItems.Count), item);
					}
				}
				Storage.Changed.Invoke(inventory, new object[2] { false, false });
				throw;
			}
			Storage.Changed.Invoke(inventory, new object[2] { false, false });
		}

		internal static bool Fuel(Component station, Player player, ItemData supplied, ItemDrop fuel, int capacity, ref bool result)
		{
			Plugin instance = Plugin.Instance;
			result = false;
			if (!StationAccess.Ready(station, player))
			{
				return false;
			}
			string name = fuel.m_itemData.m_shared.m_name;
			if (supplied != null && supplied.m_shared.m_name != name)
			{
				return true;
			}
			MethodInfo getter = AccessTools.Method(((object)station).GetType(), "GetFuel", (Type[])null, (Type[])null);
			float before = (float)getter.Invoke(station, null);
			if (float.IsNaN(before) || float.IsInfinity(before) || before < 0f || before > (float)(capacity - 1))
			{
				instance.Tell("Station fuel is full or unavailable; nothing consumed.");
				return false;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			Func<ItemData> func = () => ((IEnumerable<ItemData>)inventory.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)((ItemData i) => i.m_stack > 0 && i.m_shared.m_name == name && i.m_worldLevel >= Game.m_worldLevel));
			ItemData val = supplied ?? func();
			if (val == null && instance.Prepare(player, new List<Need>
			{
				new Need
				{
					Name = name,
					Amount = 1
				}
			}))
			{
				val = func();
			}
			if (val == null)
			{
				return false;
			}
			MethodInfo add = AccessTools.Method(((object)station).GetType(), "RPC_AddFuel", new Type[1] { typeof(long) }, (Type[])null);
			Move(inventory, val, () => StationAccess.Ready(station, player), () => (float)getter.Invoke(station, null) != before, delegate
			{
				add.Invoke(station, new object[1] { ZDOMan.GetSessionID() });
			});
			result = true;
			return false;
		}

		internal static bool Input(CookingStation station, Player player, ItemData item, ref bool result)
		{
			result = false;
			if (!StationAccess.Ready((Component)(object)station, player))
			{
				return false;
			}
			if (item == null || !Object.op_Implicit((Object)(object)item.m_dropPrefab))
			{
				return false;
			}
			if (station.m_incompatibleItems.Any((ItemMessage c) => Object.op_Implicit((Object)(object)c.m_item) && c.m_item.m_itemData.m_shared.m_name == item.m_shared.m_name))
			{
				return true;
			}
			MethodInfo methodInfo = AccessTools.Method(typeof(CookingStation), "IsItemAllowed", new Type[1] { typeof(ItemData) }, (Type[])null);
			if (!(bool)methodInfo.Invoke(station, new object[1] { item }))
			{
				return true;
			}
			int slot = (int)AccessTools.Method(typeof(CookingStation), "GetFreeSlot", (Type[])null, (Type[])null).Invoke(station, null);
			if (slot < 0)
			{
				return false;
			}
			ZDO zdo = ((Component)station).GetComponent<ZNetView>().GetZDO();
			Move(((Humanoid)player).GetInventory(), item, () => StationAccess.Ready((Component)(object)station, player), () => zdo.GetString("slot" + slot, "") != "", delegate
			{
				AccessTools.Method(typeof(CookingStation), "RPC_AddItem", (Type[])null, (Type[])null).Invoke(station, new object[3]
				{
					ZDOMan.GetSessionID(),
					((Object)item.m_dropPrefab).name,
					item.m_cheated
				});
			});
			result = true;
			return false;
		}
	}
	internal sealed class Entry
	{
		internal Container Chest;

		internal ZNetView View;

		internal Inventory Inventory;

		internal StorageKind Kind;

		internal Vector3 Position;

		internal bool Parked;

		internal int X;

		internal int Z;

		internal bool Dirty = true;

		internal bool Allowed;

		internal uint Revision;

		internal float NextCheck;

		internal readonly Dictionary<string, int> Counts = new Dictionary<string, int>();

		internal readonly Dictionary<string, ItemData> Examples = new Dictionary<string, ItemData>();

		internal Action Changed;
	}
	public sealed class ChestLifetime : MonoBehaviour
	{
		internal Container Chest;

		private void OnDestroy()
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Plugin.Instance.Storage.Remove(Chest);
			}
		}
	}
	internal sealed class Storage : IDisposable
	{
		internal static readonly MethodInfo Load = AccessTools.Method(typeof(Container), "Load", Type.EmptyTypes, (Type[])null);

		internal static readonly MethodInfo Access = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null);

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

		private readonly Plugin plugin;

		private readonly Dictionary<Container, Entry> entries = new Dictionary<Container, Entry>();

		private readonly Dictionary<long, List<Entry>> cells = new Dictionary<long, List<Entry>>();

		private readonly List<Entry> mobile = new List<Entry>();

		private readonly List<Entry> nearby = new List<Entry>();

		private readonly Dictionary<string, int> totals = new Dictionary<string, int>();

		private readonly ChestAccessRoutes<Container> accessRoutes = new ChestAccessRoutes<Container>();

		private readonly ActivityWindow activity = new ActivityWindow();

		private readonly Stopwatch timer = new Stopwatch();

		private float nextNearby;

		private Vector3 origin;

		private int cursor;

		private bool aggregateDirty = true;

		internal long Refreshes;

		internal long InventoryRebuilds;

		internal long Requests;

		private Dictionary<Container, OpenRequest> requests => accessRoutes.Requests;

		internal Storage(Plugin owner)
		{
			plugin = owner;
		}

		private static long CellKey(int x, int z)
		{
			return ((long)x << 32) ^ (uint)z;
		}

		internal static string Key(string name, int quality)
		{
			return name + "\n" + quality;
		}

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

		private static StorageKind Kind(Container chest, ZNetView view)
		{
			if ((Object)(object)chest == (Object)null || (Object)(object)view == (Object)null)
			{
				return StorageKind.Unsupported;
			}
			StorageKind storageKind = Policy.StorageType(((Object)view).name);
			if (Policy.Mobile(storageKind))
			{
				if ((Object)(object)chest.m_rootObjectOverride != (Object)(object)view || !((Component)chest).transform.IsChildOf(((Component)view).transform) || (storageKind == StorageKind.Cart && ((Object)(object)((Component)view).GetComponent<Vagon>() == (Object)null || (Object)(object)chest.m_wagon != (Object)(object)((Component)view).GetComponent<Vagon>())) || (storageKind == StorageKind.Boat && (Object)(object)((Component)view).GetComponent<Ship>() == (Object)null))
				{
					return StorageKind.Unsupported;
				}
			}
			else if ((Object)(object)((Component)chest).gameObject != (Object)(object)((Component)view).gameObject)
			{
				return StorageKind.Unsupported;
			}
			return storageKind;
		}

		private bool SourceEnabled(StorageKind kind)
		{
			return Policy.StorageEnabled(kind, plugin.Wardrobes.Value, plugin.Carts.Value, plugin.Boats.Value);
		}

		private static bool Parked(ZNetView view, StorageKind kind)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: 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_003c: Unknown result type (might be due to invalid IL or missing references)
			if (!Policy.Mobile(kind))
			{
				return true;
			}
			if ((Object)(object)view == (Object)null)
			{
				return false;
			}
			Rigidbody component = ((Component)view).GetComponent<Rigidbody>();
			if ((Object)(object)component == (Object)null || component.isKinematic)
			{
				return false;
			}
			Vector3 linearVelocity = component.linearVelocity;
			Vector3 angularVelocity = component.angularVelocity;
			if (kind == StorageKind.Boat)
			{
				linearVelocity.y = 0f;
				angularVelocity.x = 0f;
				angularVelocity.z = 0f;
			}
			return Policy.Stationary(((Vector3)(ref linearVelocity)).sqrMagnitude, ((Vector3)(ref angularVelocity)).sqrMagnitude);
		}

		internal void Add(Container chest)
		{
			//IL_009a: 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_00ba: 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)
			if ((Object)(object)chest == (Object)null || entries.ContainsKey(chest))
			{
				return;
			}
			ZNetView val = View(chest);
			StorageKind storageKind = Kind(chest, val);
			if (storageKind == StorageKind.Unsupported)
			{
				return;
			}
			Piece component = ((Component)val).GetComponent<Piece>();
			if (!((Object)(object)val == (Object)null) && val.IsValid() && !((Object)(object)component == (Object)null) && chest.GetInventory() != null)
			{
				Entry entry = new Entry
				{
					Chest = chest,
					View = val,
					Inventory = chest.GetInventory(),
					Kind = storageKind,
					Position = ((Component)chest).transform.position,
					Parked = Parked(val, storageKind),
					X = Policy.Cell(((Component)chest).transform.position.x),
					Z = Policy.Cell(((Component)chest).transform.position.z)
				};
				entry.Changed = delegate
				{
					entry.Dirty = true;
					aggregateDirty = true;
				};
				Inventory inventory = entry.Inventory;
				inventory.m_onChanged = (Action)Delegate.Combine(inventory.m_onChanged, entry.Changed);
				entries.Add(chest, entry);
				long key = CellKey(entry.X, entry.Z);
				if (!cells.TryGetValue(key, out var value))
				{
					value = (cells[key] = new List<Entry>());
				}
				value.Add(entry);
				if (Policy.Mobile(storageKind))
				{
					mobile.Add(entry);
				}
				ChestLifetime chestLifetime = ((Component)chest).GetComponent<ChestLifetime>();
				if ((Object)(object)chestLifetime == (Object)null)
				{
					chestLifetime = ((Component)chest).gameObject.AddComponent<ChestLifetime>();
				}
				chestLifetime.Chest = chest;
				nextNearby = 0f;
			}
		}

		internal void Remove(Container chest)
		{
			if (object.ReferenceEquals(chest, null))
			{
				return;
			}
			accessRoutes.Remove(chest);
			if (!entries.TryGetValue(chest, out var value))
			{
				return;
			}
			Inventory inventory = value.Inventory;
			inventory.m_onChanged = (Action)Delegate.Remove(inventory.m_onChanged, value.Changed);
			entries.Remove(chest);
			long key = CellKey(value.X, value.Z);
			if (cells.TryGetValue(key, out var value2))
			{
				value2.Remove(value);
				if (value2.Count == 0)
				{
					cells.Remove(key);
				}
			}
			nearby.Remove(value);
			mobile.Remove(value);
			nextNearby = 0f;
			aggregateDirty = true;
		}

		internal bool Allowed(Container chest, Player player, float radius, bool requireOwner)
		{
			//IL_0018: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Invalid comparison between Unknown and I4
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)chest == (Object)null) && !((Object)(object)player == (Object)null))
			{
				Vector3 val = ((Component)chest).transform.position - ((Component)player).transform.position;
				if (Policy.InRange(((Vector3)(ref val)).sqrMagnitude, radius))
				{
					ZNetView val2 = View(chest);
					StorageKind kind = Kind(chest, val2);
					if (!SourceEnabled(kind))
					{
						return false;
					}
					Piece component = ((Component)val2).GetComponent<Piece>();
					if ((Object)(object)component == (Object)null || !component.IsPlacedByPlayer())
					{
						return false;
					}
					if (!val2.IsValid() || ((requireOwner || Policy.Mobile(kind)) && !val2.IsOwner()))
					{
						return false;
					}
					if (!Parked(val2, kind))
					{
						return false;
					}
					if (chest.IsInUse() || val2.GetZDO().GetInt(ZDOVars.s_inUse, 0) != 0)
					{
						return false;
					}
					if (Policy.Mobile(kind) && (int)chest.m_privacy != 2)
					{
						return false;
					}
					if ((bool)Access.Invoke(chest, new object[1] { player.GetPlayerID() }))
					{
						return PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, true);
					}
					return false;
				}
			}
			return false;
		}

		internal List<Entry> Nearby(Player player, bool activate = true)
		{
			//IL_005a: 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_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			if (activate)
			{
				activity.Touch(Time.unscaledTime);
			}
			UpdateMobilePositions();
			if (Time.unscaledTime < nextNearby)
			{
				Vector3 val = origin - ((Component)player).transform.position;
				if (((Vector3)(ref val)).sqrMagnitude < 0.25f)
				{
					return nearby;
				}
			}
			origin = ((Component)player).transform.position;
			nextNearby = Time.unscaledTime + 0.5f;
			nearby.Clear();
			float value = plugin.Range.Value;
			for (int i = Policy.Cell(origin.x - value); i <= Policy.Cell(origin.x + value); i++)
			{
				for (int j = Policy.Cell(origin.z - value); j <= Policy.Cell(origin.z + value); j++)
				{
					if (!cells.TryGetValue(CellKey(i, j), out var value2))
					{
						continue;
					}
					foreach (Entry item in value2)
					{
						if ((Object)(object)item.Chest != (Object)null && item.Parked && SourceEnabled(item.Kind))
						{
							Vector3 val2 = ((Component)item.Chest).transform.position - origin;
							if (Policy.InRange(((Vector3)(ref val2)).sqrMagnitude, value))
							{
								nearby.Add(item);
							}
						}
					}
				}
			}
			nearby.Sort(delegate(Entry a, Entry b)
			{
				//IL_000b: 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_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: 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_003c: 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)
				Vector3 val3 = ((Component)a.Chest).transform.position - origin;
				float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude;
				Vector3 val4 = ((Component)b.Chest).transform.position - origin;
				return sqrMagnitude.CompareTo(((Vector3)(ref val4)).sqrMagnitude);
			});
			aggregateDirty = true;
			return nearby;
		}

		private void UpdateMobilePositions()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			for (int num = mobile.Count - 1; num >= 0; num--)
			{
				Entry entry = mobile[num];
				if ((Object)(object)entry.Chest == (Object)null)
				{
					Remove(entry.Chest);
				}
				else
				{
					Vector3 position = ((Component)entry.Chest).transform.position;
					bool flag = Parked(entry.View, entry.Kind);
					if (!((Vector3)(ref position)).Equals(entry.Position) || flag != entry.Parked)
					{
						int num2 = Policy.Cell(position.x);
						int num3 = Policy.Cell(position.z);
						if (num2 != entry.X || num3 != entry.Z)
						{
							long key = CellKey(entry.X, entry.Z);
							if (cells.TryGetValue(key, out var value))
							{
								value.Remove(entry);
								if (value.Count == 0)
								{
									cells.Remove(key);
								}
							}
							long key2 = CellKey(num2, num3);
							if (!cells.TryGetValue(key2, out value))
							{
								value = (cells[key2] = new List<Entry>());
							}
							value.Add(entry);
							entry.X = num2;
							entry.Z = num3;
						}
						entry.Position = position;
						entry.Parked = flag;
						entry.NextCheck = 0f;
						nextNearby = 0f;
						aggregateDirty = true;
					}
				}
			}
		}

		internal void Tick(Player player)
		{
			if ((Object)(object)player == (Object)null || !activity.IsActive(Time.unscaledTime))
			{
				return;
			}
			Nearby(player, activate: false);
			timer.Restart();
			int num = 0;
			while (nearby.Count > 0 && num++ < nearby.Count && timer.Elapsed.TotalMilliseconds < (double)plugin.Budget.Value)
			{
				if (cursor >= nearby.Count)
				{
					cursor = 0;
				}
				Entry entry = nearby[cursor++];
				if ((Object)(object)entry.Chest == (Object)null)
				{
					Remove(entry.Chest);
				}
				else if ((entry.Dirty && entry.Allowed) || Time.unscaledTime >= entry.NextCheck)
				{
					Refresh(entry, player);
				}
			}
		}

		internal void Refresh(Entry entry, Player player)
		{
			Refreshes++;
			entry.NextCheck = Time.unscaledTime + plugin.Refresh.Value;
			bool flag = Allowed(entry.Chest, player, plugin.Range.Value, requireOwner: false);
			if (entry.Allowed != flag)
			{
				aggregateDirty = true;
			}
			entry.Allowed = flag;
			if (!flag)
			{
				return;
			}
			uint dataRevision = entry.View.GetZDO().DataRevision;
			if (!entry.Dirty && dataRevision == entry.Revision)
			{
				return;
			}
			Load.Invoke(entry.Chest, null);
			entry.Counts.Clear();
			entry.Examples.Clear();
			foreach (ItemData allItem in entry.Inventory.GetAllItems())
			{
				if (allItem.m_stack > 0 && allItem.m_worldLevel >= Game.m_worldLevel)
				{
					AddCount(entry.Counts, Key(allItem.m_shared.m_name, allItem.m_quality), allItem.m_stack);
					AddCount(entry.Counts, Key(allItem.m_shared.m_name, -1), allItem.m_stack);
					entry.Examples[Key(allItem.m_shared.m_name, allItem.m_quality)] = allItem;
					entry.Examples[Key(allItem.m_shared.m_name, -1)] = allItem;
				}
			}
			entry.Revision = entry.View.GetZDO().DataRevision;
			entry.Dirty = false;
			aggregateDirty = true;
			InventoryRebuilds++;
		}

		private static void AddCount(Dictionary<string, int> counts, string key, int value)
		{
			counts.TryGetValue(key, out var value2);
			counts[key] = checked(value2 + value);
		}

		internal int Count(Player player, string name, int quality)
		{
			Nearby(player);
			if (aggregateDirty)
			{
				totals.Clear();
				foreach (Entry item in nearby)
				{
					if (!item.Allowed || !((Object)(object)item.Chest != (Object)null))
					{
						continue;
					}
					foreach (KeyValuePair<string, int> count in item.Counts)
					{
						AddCount(totals, count.Key, count.Value);
					}
				}
				aggregateDirty = false;
			}
			totals.TryGetValue(Key(name, quality), out var value);
			return value;
		}

		internal ItemData Example(Player player, string name, int quality)
		{
			foreach (Entry item in Nearby(player))
			{
				if (item.Allowed && item.Examples.TryGetValue(Key(name, quality), out var value))
				{
					return value.Clone();
				}
			}
			return null;
		}

		internal bool Ready(Container chest, Player player, float radius)
		{
			if (!Allowed(chest, player, radius, requireOwner: false))
			{
				plugin.Tell("Storage disabled, busy, protected, out of range or vehicle moving/not locally owned; nothing pulled.");
				return false;
			}
			ZNetView val = View(chest);
			if (requests.TryGetValue(chest, out var value))
			{
				if (value.Ready(val.IsOwner(), Time.unscaledTime))
				{
					plugin.AccessLog("approval consumed on action retry", chest, value.Owner);
					requests.Remove(chest);
					return true;
				}
				if (!value.TimedOut(Time.unscaledTime))
				{
					return false;
				}
				requests.Remove(chest);
			}
			if (val.IsOwner())
			{
				return true;
			}
			if (!val.HasOwner())
			{
				plugin.Tell("Chest has no confirmed owner yet; retry shortly.");
				return false;
			}
			accessRoutes.BeginAssisted(chest);
			requests[chest] = new OpenRequest
			{
				Owner = val.GetZDO().GetOwner(),
				Started = Time.unscaledTime
			};
			Requests++;
			plugin.AccessLog("request sent", chest, val.GetZDO().GetOwner());
			val.InvokeRPC("RPC_RequestOpen", new object[1] { player.GetPlayerID() });
			plugin.Tell(plugin.AutomaticCraftContext ? "Waiting for chest access; crafting will continue automatically." : "Requesting chest access; retry the action shortly.");
			return false;
		}

		internal bool Response(Container chest, long sender, bool granted)
		{
			if (!entries.ContainsKey(chest))
			{
				return true;
			}
			if (accessRoutes.IsNative(chest))
			{
				accessRoutes.CompleteManual(chest);
				plugin.AccessLog("manual reply passed through; granted=" + granted, chest, sender);
				return true;
			}
			if (!requests.TryGetValue(chest, out var value))
			{
				return !accessRoutes.WasAssisted(chest);
			}
			if (!value.Accept(sender, granted, Time.unscaledTime))
			{
				plugin.AccessLog("ignored mismatched or duplicate reply", chest, sender);
				return false;
			}
			plugin.AccessLog("crafting reply granted=" + granted + "; elapsed=" + (Time.unscaledTime - value.Started), chest, sender);
			return false;
		}

		internal void ManualOpen(Container chest)
		{
			plugin.CancelPending("chest opened manually");
			if (entries.ContainsKey(chest) && accessRoutes.UseNative(chest))
			{
				plugin.AccessLog("manual interaction takes priority for this interaction", chest, 0L);
			}
		}

		internal void Invalidate()
		{
			nextNearby = 0f;
			aggregateDirty = true;
			foreach (Entry value in entries.Values)
			{
				value.NextCheck = 0f;
			}
		}

		internal bool CanAwait(List<Container> sources)
		{
			foreach (Container source in sources)
			{
				if ((Object)(object)source == (Object)null || !entries.ContainsKey(source) || !Allowed(source, Player.m_localPlayer, plugin.Range.Value, requireOwner: false))
				{
					return false;
				}
				ZNetView val = View(source);
				if ((Object)(object)val == (Object)null || !val.IsValid())
				{
					return false;
				}
				if (requests.TryGetValue(source, out var value))
				{
					if (value.TimedOut(Time.unscaledTime))
					{
						return false;
					}
				}
				else if (!val.IsOwner())
				{
					return false;
				}
			}
			return true;
		}

		internal bool AwaitReady(List<Container> sources)
		{
			foreach (Container source in sources)
			{
				if (!Allowed(source, Player.m_localPlayer, plugin.Range.Value, requireOwner: true))
				{
					return false;
				}
				ZNetView val = View(source);
				if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner())
				{
					return false;
				}
				if (requests.TryGetValue(source, out var value) && !value.Ready(owned: true, Time.unscaledTime))
				{
					return false;
				}
			}
			return true;
		}

		public void Dispose()
		{
			foreach (Entry value in entries.Values)
			{
				Inventory inventory = value.Inventory;
				inventory.m_onChanged = (Action)Delegate.Remove(inventory.m_onChanged, value.Changed);
			}
			entries.Clear();
			cells.Clear();
			mobile.Clear();
			nearby.Clear();
			totals.Clear();
			accessRoutes.Clear();
		}
	}
	internal sealed class Move<T> where T : class
	{
		internal IList<T> Source;

		internal IList<T> Destination;

		internal T Item;

		internal T Copy;

		internal int Amount;
	}
	internal static class TransferBatch
	{
		internal static void Execute<T>(IList<Move<T>> moves, Func<T, int> count, Action<T, int> setCount, Action<IList<T>> publish) where T : class
		{
			Dictionary<IList<T>, List<T>> dictionary = new Dictionary<IList<T>, List<T>>();
			Dictionary<T, int> dictionary2 = new Dictionary<T, int>();
			Dictionary<T, int> dictionary3 = new Dictionary<T, int>();
			HashSet<T> hashSet = new HashSet<T>();
			foreach (Move<T> move in moves)
			{
				if (move.Amount <= 0 || move.Item == null || move.Copy == null || object.ReferenceEquals(move.Item, move.Copy) || object.ReferenceEquals(move.Source, move.Destination) || !move.Source.Contains(move.Item) || move.Destination.Contains(move.Copy) || !hashSet.Add(move.Copy))
				{
					throw new InvalidOperationException("Invalid transfer plan.");
				}
				dictionary3.TryGetValue(move.Item, out var value);
				value = checked(value + move.Amount);
				if (value > count(move.Item))
				{
					throw new InvalidOperationException("Stock changed before transfer.");
				}
				dictionary3[move.Item] = value;
				dictionary2[move.Item] = count(move.Item);
				if (!dictionary.ContainsKey(move.Source))
				{
					dictionary[move.Source] = new List<T>(move.Source);
				}
				if (!dictionary.ContainsKey(move.Destination))
				{
					dictionary[move.Destination] = new List<T>(move.Destination);
				}
			}
			try
			{
				foreach (Move<T> move2 in moves)
				{
					setCount(move2.Item, count(move2.Item) - move2.Amount);
					if (count(move2.Item) == 0)
					{
						move2.Source.Remove(move2.Item);
					}
					setCount(move2.Copy, move2.Amount);
					move2.Destination.Add(move2.Copy);
				}
				foreach (IList<T> key in dictionary.Keys)
				{
					publish(key);
				}
			}
			catch (Exception item)
			{
				foreach (KeyValuePair<T, int> item3 in dictionary2)
				{
					setCount(item3.Key, item3.Value);
				}
				foreach (KeyValuePair<IList<T>, List<T>> item4 in dictionary)
				{
					item4.Key.Clear();
					foreach (T item5 in item4.Value)
					{
						item4.Key.Add(item5);
					}
				}
				List<Exception> list = new List<Exception>();
				list.Add(item);
				List<Exception> list2 = list;
				foreach (IList<T> key2 in dictionary.Keys)
				{
					try
					{
						publish(key2);
					}
					catch (Exception item2)
					{
						list2.Add(item2);
					}
				}
				throw new AggregateException("Transfer failed; original inventory references restored. Inspect inner errors for persistence failures.", list2);
			}
		}
	}
}
namespace ReliableStationSafety
{
	public sealed class PendingFood : MonoBehaviour
	{
		internal bool Waiting;

		internal string Input;

		internal string Output;

		internal long Owner;

		internal string[] Before;

		internal float Started;

		internal int AcceptedSlot = -1;

		internal void Observe(long sender, int slot, string item)
		{
			if (Waiting && sender == Owner && slot >= 0 && slot < Before.Length)
			{
				if (Before[slot] == "" && (item == Input || item == Output))
				{
					AcceptedSlot = slot;
				}
				if (slot == AcceptedSlot && item == "")
				{
					Waiting = false;
				}
			}
		}

		internal bool Confirmed(ZDO state)
		{
			if (!Waiting)
			{
				return true;
			}
			for (int i = 0; i < Before.Length; i++)
			{
				string text = state.GetString("slot" + i, "");
				if (Before[i] == "" && (text == Input || text == Output))
				{
					Waiting = false;
					return true;
				}
			}
			return false;
		}
	}
	internal sealed class FoodAttempt
	{
		internal PendingFood Pending;

		internal Inventory Inventory;

		internal string Name;

		internal int Count;
	}
	internal static class RemoteFoodInput
	{
		private const string CapacityKey = "reliable_workstations_food_capacity";

		private const string OwnerKey = "reliable_workstations_food_capacity_owner";

		internal static int NativeCapacity(string name)
		{
			name = (name ?? "").Replace("(Clone)", "").Trim();
			return name switch
			{
				"piece_cookingstation" => 2, 
				"piece_cookingstation_iron" => 5, 
				"piece_oven" => 4, 
				_ => 0, 
			};
		}

		internal static void Publish(CookingStation station)
		{
			if (NativeCapacity(((Object)station).name) != 0 && station.m_slots != null)
			{
				ZNetView component = ((Component)station).GetComponent<ZNetView>();
				if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner())
				{
					ZDO zDO = component.GetZDO();
					zDO.Set("reliable_workstations_food_capacity", station.m_slots.Length);
					zDO.Set("reliable_workstations_food_capacity_owner", zDO.GetOwner());
				}
			}
		}

		internal static void ObserveSnapshot(CookingStation station)
		{
			PendingFood component = ((Component)station).GetComponent<PendingFood>();
			if (!((Object)(object)component == (Object)null) && component.Waiting)
			{
				ZNetView component2 = ((Component)station).GetComponent<ZNetView>();
				if ((Object)(object)component2 != (Object)null && component2.IsValid())
				{
					component.Confirmed(component2.GetZDO());
				}
			}
		}

		internal static bool Begin(CookingStation station, Player player, ItemData item, out FoodAttempt attempt, out string message)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			attempt = null;
			message = null;
			ZNetView component = ((Component)station).GetComponent<ZNetView>();
			if (item == null || (Object)(object)item.m_dropPrefab == (Object)null || !((Humanoid)player).GetInventory().ContainsItem(item) || item.m_stack <= 0 || (Object)(object)component == (Object)null || !component.IsValid() || !component.HasOwner() || ((Character)player).IsDead() || ((Character)player).IsTeleporting() || !PrivateArea.CheckAccess(((Component)station).transform.position, 0f, false, true))
			{
				return false;
			}
			ZDO zDO = component.GetZDO();
			PendingFood pendingFood = ((Component)station).GetComponent<PendingFood>();
			if ((Object)(object)pendingFood == (Object)null)
			{
				pendingFood = ((Component)station).gameObject.AddComponent<PendingFood>();
			}
			if (!pendingFood.Confirmed(zDO))
			{
				if (Time.unscaledTime - pendingFood.Started > 3f)
				{
					message = "Waiting for the station's previous food input to appear. Remaining food is still in your inventory.";
				}
				return false;
			}
			int num = NativeCapacity(((Object)station).name);
			int num2 = zDO.GetInt("reliable_workstations_food_capacity", 0);
			if (zDO.GetLong("reliable_workstations_food_capacity_owner", 0L) == zDO.GetOwner() && num2 >= num && num2 <= 32)
			{
				num = num2;
			}
			num = Math.Min(num, station.m_slots.Length);
			string[] array = new string[num];
			bool flag = false;
			for (int i = 0; i < num; i++)
			{
				array[i] = zDO.GetString("slot" + i, "");
				if (array[i] == "")
				{
					flag = true;
				}
			}
			if (!flag)
			{
				message = "The station's confirmed cooking slots are full. Remaining food is still in your inventory.";
				return false;
			}
			string text = null;
			foreach (ItemConversion item2 in station.m_conversion)
			{
				if ((Object)(object)item2.m_from != (Object)null && ((Object)item2.m_from).name == ((Object)item.m_dropPrefab).name)
				{
					text = (((Object)(object)item2.m_to != (Object)null) ? ((Object)item2.m_to).name : null);
					break;
				}
			}
			if (text == null)
			{
				return true;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			pendingFood.Input = ((Object)item.m_dropPrefab).name;
			pendingFood.Output = text;
			pendingFood.Before = array;
			pendingFood.Owner = zDO.GetOwner();
			pendingFood.Started = Time.unscaledTime;
			pendingFood.AcceptedSlot = -1;
			pendingFood.Waiting = true;
			attempt = new FoodAttempt
			{
				Pending = pendingFood,
				Inventory = inventory,
				Name = item.m_shared.m_name,
				Count = inventory.CountItems(item.m_shared.m_name, -1, true)
			};
			return true;
		}

		internal static void Finish(FoodAttempt attempt)
		{
			if (attempt != null && (Object)(object)attempt.Pending != (Object)null && attempt.Inventory.CountItems(attempt.Name, -1, true) >= attempt.Count)
			{
				attempt.Pending.Waiting = false;
			}
		}
	}
	[HarmonyPatch(typeof(CookingStation), "CookItem")]
	internal static class FoodInputObservation
	{
		[HarmonyPriority(801)]
		private static void Prefix(CookingStation __instance)
		{
			RemoteFoodInput.ObserveSnapshot(__instance);
		}
	}
	[HarmonyPatch(typeof(CookingStation), "RPC_SetSlotVisual")]
	internal static class FoodVisualObservation
	{
		private static void Postfix(CookingStation __instance, long __0, int __1, string __2)
		{
			PendingFood component = ((Component)__instance).GetComponent<PendingFood>();
			if ((Object)(object)component != (Object)null)
			{
				component.Observe(__0, __1, __2);
			}
		}
	}
	[HarmonyPatch(typeof(CookingStation), "UpdateCooking")]
	internal static class FoodCapacityAdvertisement
	{
		private static void Prefix(CookingStation __instance)
		{
			RemoteFoodInput.ObserveSnapshot(__instance);
		}

		private static void Postfix(CookingStation __instance)
		{
			RemoteFoodInput.Publish(__instance);
		}
	}
}