Decompiled source of CookFromStorage v1.0.0

plugins/CookFromStorage.dll

Decompiled an hour ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using ValheimMods.Common;
using ValheimMods.Common.Crafting;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: AssemblyCompany("Hoose")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) Hoose 2026")]
[assembly: AssemblyDescription("Adds cooking ingredients from nearby storage to cooking stations.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+896f20ee711cb5c9c1e236c5fb2138f9b73eb0d1")]
[assembly: AssemblyProduct("Valheim CookFromStorage")]
[assembly: AssemblyTitle("CookFromStorage")]
[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 CookFromStorage
{
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class ContainerCreatedPatch
	{
		[HarmonyPostfix]
		private static void AfterAwake(Container __instance)
		{
			NearbyStorage.Add(__instance);
		}
	}
	[HarmonyPatch(typeof(Container), "OnDestroyed")]
	internal static class ContainerDestroyedPatch
	{
		[HarmonyPrefix]
		private static void BeforeDestroy(Container __instance)
		{
			NearbyStorage.Remove(__instance);
		}
	}
	[BepInPlugin("hoose.valheim.cookfromstorage", "CookFromStorage", "1.0.0")]
	[BepInProcess("valheim.exe")]
	public sealed class CookFromStoragePlugin : BaseUnityPlugin
	{
		public const string PluginName = "CookFromStorage";

		public const string PluginAuthor = "Hoose";

		public const string PluginGuid = "hoose.valheim.cookfromstorage";

		public const string PluginVersion = "1.0.0";

		private const string StorageRadiusDescription = "How far, in meters, CookFromStorage searches for storage around you (a sphere centred on your character). Allowed range: 1 to 100.\nApplies to food recipes, cooking stations, the Stone Oven and the Fermenter. Only storage you could normally open is used.";

		private const string AutomaticIngredientsDescription = "Use accessible storage within 'Storage radius' for food and brewing ingredients you do not have in your inventory.\nCovers:\n- Recipes at the Cauldron, Food Preparation Table and Mead Ketill (storage counts toward requirements and is consumed when you craft).\n- Raw food for the Cooking Station, Iron Cooking Station and Stone Oven: one item per E press.\n- Mead bases for an empty Fermenter: one per E press.\nYour inventory is always used first. Frost Foundry casts are never loaded from storage. Fuel is handled by SupplyFromStorage, not this setting.";

		private static ConfigEntry<bool> _automaticIngredients;

		private static ConfigEntry<float> _storageRadius;

		private Harmony _harmony;

		private RecipeStorage _recipeStorage;

		internal static ManualLogSource Log { get; private set; }

		internal static bool AutomaticIngredientsEnabled => _automaticIngredients?.Value ?? false;

		internal static float StorageRadius => _storageRadius?.Value ?? 20f;

		private void Awake()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_storageRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Storage", "Storage radius", 20f, new ConfigDescription("How far, in meters, CookFromStorage searches for storage around you (a sphere centred on your character). Allowed range: 1 to 100.\nApplies to food recipes, cooking stations, the Stone Oven and the Fermenter. Only storage you could normally open is used.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>()));
			_automaticIngredients = ((BaseUnityPlugin)this).Config.Bind<bool>("Automation", "Automatic ingredients", true, "Use accessible storage within 'Storage radius' for food and brewing ingredients you do not have in your inventory.\nCovers:\n- Recipes at the Cauldron, Food Preparation Table and Mead Ketill (storage counts toward requirements and is consumed when you craft).\n- Raw food for the Cooking Station, Iron Cooking Station and Stone Oven: one item per E press.\n- Mead bases for an empty Fermenter: one per E press.\nYour inventory is always used first. Frost Foundry casts are never loaded from storage. Fuel is handled by SupplyFromStorage, not this setting.");
			NearbyStorage.Initialize();
			_harmony = new Harmony("hoose.valheim.cookfromstorage");
			_harmony.PatchAll(typeof(CookFromStoragePlugin).Assembly);
			MethodInfo methodInfo = AccessTools.Method(typeof(CookingStation), "OnInteract", new Type[1] { typeof(Humanoid) }, (Type[])null);
			bool num = ((methodInfo == null) ? null : Harmony.GetPatchInfo((MethodBase)methodInfo))?.Prefixes.Any((Patch patch) => patch.owner == "hoose.valheim.cookfromstorage" && patch.PatchMethod.DeclaringType == typeof(CookingStationPatch)) ?? false;
			bool flag = IsRegistered(typeof(Container), "Awake", typeof(ContainerCreatedPatch)) && IsRegistered(typeof(Container), "OnDestroyed", typeof(ContainerDestroyedPatch));
			bool flag2 = IsRegistered(typeof(Fermenter), "Interact", typeof(FermenterPatch));
			if (!num || !flag || !flag2)
			{
				_harmony.UnpatchSelf();
				NearbyStorage.Clear();
				Log.LogError((object)"CookFromStorage: one or more cooking/storage patches were not registered; automation is disabled.");
				return;
			}
			try
			{
				_recipeStorage = RecipeStorage.Register(true, (Action<IEnumerator>)delegate(IEnumerator coroutine)
				{
					((MonoBehaviour)this).StartCoroutine(coroutine);
				}, (Action)((MonoBehaviour)this).StopAllCoroutines, (Action<string>)delegate(string message)
				{
					Log.LogWarning((object)message);
				}, (Action<string>)delegate(string message)
				{
					Log.LogError((object)message);
				}, (Func<bool>)(() => AutomaticIngredientsEnabled), (Func<float>)(() => StorageRadius));
			}
			catch (Exception arg)
			{
				_harmony.UnpatchSelf();
				NearbyStorage.Clear();
				Log.LogError((object)string.Format("{0}: shared food recipe-storage initialization failed. {1}", "CookFromStorage", arg));
				throw;
			}
			Log.LogInfo((object)string.Format("{0} {1} loaded; food recipes, cooking-station inputs and Fermenter bases use storage ({2:0.#}m).", "CookFromStorage", "1.0.0", NearbyStorage.Radius));
		}

		private void OnDestroy()
		{
			RecipeStorage recipeStorage = _recipeStorage;
			if (recipeStorage != null)
			{
				recipeStorage.Dispose();
			}
			((MonoBehaviour)this).StopAllCoroutines();
			NearbyStorage.Clear();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if (Log == ((BaseUnityPlugin)this).Logger)
			{
				Log = null;
			}
		}

		private static bool IsRegistered(Type targetType, string methodName, Type patchType)
		{
			MethodInfo methodInfo = AccessTools.Method(targetType, methodName, (Type[])null, (Type[])null);
			Patches val = ((methodInfo == null) ? null : Harmony.GetPatchInfo((MethodBase)methodInfo));
			return val?.Prefixes.Concat(val.Postfixes).Any((Patch patch) => patch.owner == "hoose.valheim.cookfromstorage" && patch.PatchMethod.DeclaringType == patchType) ?? false;
		}
	}
	[HarmonyPatch(typeof(CookingStation), "OnInteract")]
	internal static class CookingStationPatch
	{
		[HarmonyPrefix]
		private static bool BeforeFoodRequested(CookingStation __instance, Humanoid user, ref bool __result)
		{
			if (!CookingTransfer.TryHandleFoodRequest(__instance, user, out var handledResult))
			{
				return true;
			}
			__result = handledResult;
			return false;
		}
	}
	internal static class CookingTransfer
	{
		internal enum TransferResult
		{
			Transferred,
			NoItems,
			NotReady,
			NeedsFire,
			Full,
			Failed
		}

		internal static bool TryHandleFoodRequest(CookingStation station, Humanoid user, out bool handledResult)
		{
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			handledResult = false;
			if (CookFromStoragePlugin.AutomaticIngredientsEnabled && !((Object)(object)user != (Object)(object)Player.m_localPlayer))
			{
				Player val = (Player)(object)((user is Player) ? user : null);
				if (val != null && ((Humanoid)val).GetInventory() != null && !((Object)(object)station == (Object)null) && !(station.m_name == "$piece_frostfoundry") && station.m_conversion != null)
				{
					if (!CanUse(station, val))
					{
						bool flag = (Object)(object)station.m_nview != (Object)null && station.m_nview.IsValid();
						ManualLogSource log = CookFromStoragePlugin.Log;
						string text = $"Storage cooking skipped for {((Object)station).name}: active={((Behaviour)station).isActiveAndEnabled}, ";
						string text2 = $"networkValid={flag}, ";
						string text3 = $"locallyOwned={flag && station.m_nview.IsOwner()}, ";
						Vector3 val2 = ((Component)station).transform.position - ((Component)val).transform.position;
						log.LogWarning((object)(text + text2 + text3 + $"distance={Math.Sqrt(((Vector3)(ref val2)).sqrMagnitude):F1}m."));
						return false;
					}
					if (station.HaveDoneItem() || station.FindCookableItem(((Humanoid)val).GetInventory()) != null)
					{
						return false;
					}
					List<Container> list = NearbyStorage.Find(val);
					TransferResult transferResult = TransferOneIngredient(station, val, list);
					switch (transferResult)
					{
					case TransferResult.NeedsFire:
					case TransferResult.Full:
					{
						string text5 = ((transferResult == TransferResult.NeedsFire) ? "$msg_needfire" : "$msg_nocookroom");
						((Character)val).Message((MessageType)2, text5, 0, (Sprite)null, false);
						CookFromStoragePlugin.Log.LogInfo((object)("Storage ingredient available for " + ((Object)station).name + ", but " + ((transferResult == TransferResult.NeedsFire) ? "no lit fire was detected" : "all cooking slots are full") + "."));
						return true;
					}
					case TransferResult.NoItems:
					{
						string text4 = string.Join(", ", from conversion in station.m_conversion
							where (Object)(object)conversion?.m_from != (Object)null
							select conversion.m_from.m_itemData?.m_shared?.m_name);
						CookFromStoragePlugin.Log.LogInfo((object)($"No accepted storage ingredient for {((Object)station).name} in {list.Count} accessible locally owned containers within {NearbyStorage.Radius:0.#}m. " + "Accepted item names: " + text4 + "."));
						break;
					}
					}
					if (transferResult != TransferResult.Transferred)
					{
						return false;
					}
					handledResult = true;
					return true;
				}
			}
			return false;
		}

		internal static TransferResult TransferOneIngredient(CookingStation station, Player player, IReadOnlyList<Container> sources)
		{
			if (!CanUse(station, player) || station.m_conversion == null)
			{
				return TransferResult.NotReady;
			}
			foreach (Container source in sources)
			{
				if (!StorageInputTransfer.TryGetInventory(source, player, out var inventory))
				{
					continue;
				}
				ItemData[] array = inventory.GetAllItems().ToArray();
				foreach (ItemData val in array)
				{
					if (val?.m_shared == null || val.m_stack <= 0)
					{
						continue;
					}
					foreach (ItemConversion item in station.m_conversion)
					{
						ItemDrop obj = item?.m_from;
						string text = obj?.m_itemData?.m_shared?.m_name;
						string text2 = ((obj != null) ? ((Object)obj).name : null);
						if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2) && !(val.m_shared.m_name != text) && station.IsItemAllowed(text2))
						{
							if (station.m_requireFire && !station.IsFireLit())
							{
								return TransferResult.NeedsFire;
							}
							if (station.GetFreeSlot() < 0)
							{
								return TransferResult.Full;
							}
							return ConsumeOne(source, inventory, player, station, val, text2);
						}
					}
				}
			}
			return TransferResult.NoItems;
		}

		private static TransferResult ConsumeOne(Container container, Inventory inventory, Player player, CookingStation station, ItemData item, string prefabName)
		{
			if (!StorageInputTransfer.TryGetInventory(container, player, out var inventory2) || inventory2 != inventory || !inventory.ContainsItem(item) || item.m_stack <= 0 || !CanUse(station, player) || station.GetFreeSlot() < 0 || (station.m_requireFire && !station.IsFireLit()) || !station.IsItemAllowed(prefabName))
			{
				CookFromStoragePlugin.Log.LogWarning((object)"Storage or cooking station changed before transfer; no ingredient was added.");
				return TransferResult.Failed;
			}
			int freeSlotsBefore = station.GetFreeSlot();
			if (!StorageInputTransfer.ConsumeOne(container, inventory, player, item, "Cooking station", () => CanUse(station, player) && station.GetFreeSlot() == freeSlotsBefore && (!station.m_requireFire || station.IsFireLit()) && station.IsItemAllowed(prefabName), delegate
			{
				station.RPC_AddItem(player.GetPlayerID(), prefabName, false);
			}, delegate
			{
				int freeSlot = station.GetFreeSlot();
				return freeSlot == -1 || freeSlot > freeSlotsBefore;
			}))
			{
				return TransferResult.Failed;
			}
			return TransferResult.Transferred;
		}

		private static bool CanUse(CookingStation station, Player player)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)station != (Object)null && ((Behaviour)station).isActiveAndEnabled && (Object)(object)station.m_nview != (Object)null && station.m_name != "$piece_frostfoundry" && station.m_nview.IsValid() && station.m_nview.IsOwner() && (Object)(object)player != (Object)null)
			{
				Vector3 val = ((Component)station).transform.position - ((Component)player).transform.position;
				return ((Vector3)(ref val)).sqrMagnitude <= 400f;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Fermenter), "Interact", new Type[]
	{
		typeof(Humanoid),
		typeof(bool),
		typeof(bool)
	})]
	internal static class FermenterPatch
	{
		[HarmonyPrefix]
		private static bool BeforeInteracted(Fermenter __instance, Humanoid user, bool hold, ref bool __result)
		{
			if (!FermenterTransfer.TryHandleRequest(__instance, user, hold))
			{
				return true;
			}
			__result = true;
			return false;
		}
	}
	internal static class FermenterTransfer
	{
		internal static bool TryHandleRequest(Fermenter station, Humanoid user, bool hold)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			if (!hold && CookFromStoragePlugin.AutomaticIngredientsEnabled && !((Object)(object)user != (Object)(object)Player.m_localPlayer))
			{
				Player player = (Player)(object)((user is Player) ? user : null);
				if (player != null && ((Humanoid)player).GetInventory() != null && CanUse(station, player) && (int)station.GetStatus() == 0 && station.FindCookableItem(((Humanoid)player).GetInventory()) == null && PrivateArea.CheckAccess(((Component)station).transform.position, 0f, false, false))
				{
					station.UpdateCover(0f, true);
					if (!station.m_hasRoof || station.m_exposed)
					{
						return false;
					}
					foreach (Container item2 in NearbyStorage.Find(player))
					{
						if (!StorageInputTransfer.TryGetInventory(item2, player, out var inventory))
						{
							continue;
						}
						ItemData[] array = inventory.GetAllItems().ToArray();
						foreach (ItemData item in array)
						{
							if (item?.m_shared != null && item.m_stack > 0 && !((Object)(object)item.m_dropPrefab == (Object)null) && !string.IsNullOrEmpty(((Object)item.m_dropPrefab).name) && station.IsItemAllowed(item))
							{
								int nameHash = StringExtensionMethods.GetStableHashCode(((Object)item.m_dropPrefab).name);
								return StorageInputTransfer.ConsumeOne(item2, inventory, player, item, "Fermenter", () => CanUse(station, player) && (int)station.GetStatus() == 0 && station.m_hasRoof && !station.m_exposed && station.IsItemAllowed(item) && PrivateArea.CheckAccess(((Component)station).transform.position, 0f, false, false), delegate
								{
									station.RPC_AddItem(player.GetPlayerID(), nameHash, item.m_cheated);
								}, () => station.GetContent() == nameHash);
							}
						}
					}
					return false;
				}
			}
			return false;
		}

		private static bool CanUse(Fermenter station, Player player)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: 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)
			if ((Object)(object)station != (Object)null && ((Behaviour)station).isActiveAndEnabled && (Object)(object)station.m_nview != (Object)null && station.m_nview.IsValid() && station.m_nview.IsOwner() && station.m_conversion != null && (Object)(object)player != (Object)null)
			{
				Vector3 val = ((Component)station).transform.position - ((Component)player).transform.position;
				return ((Vector3)(ref val)).sqrMagnitude <= 400f;
			}
			return false;
		}
	}
	internal static class NearbyStorage
	{
		private static readonly ContainerRegistry _registry = new ContainerRegistry();

		internal static float Radius => StorageRange.Clamp(CookFromStoragePlugin.StorageRadius);

		internal static void Initialize()
		{
			_registry.Initialize();
		}

		internal static void Add(Container container)
		{
			_registry.Add(container);
		}

		internal static void Remove(Container container)
		{
			_registry.Remove(container);
		}

		internal static void Clear()
		{
			_registry.Clear();
		}

		internal static bool IsAccessible(Container container, Player player)
		{
			return ContainerRegistry.IsAccessible(container, player, Radius, false);
		}

		internal static List<Container> Find(Player player)
		{
			//IL_0086: 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)
			List<Container> list = new List<Container>();
			if ((Object)(object)player == (Object)null)
			{
				return list;
			}
			_registry.PruneDestroyed((Action<Container>)null);
			foreach (Container container in _registry.Containers)
			{
				if (IsAccessible(container, player) && container.IsOwner())
				{
					container.Load();
					if (IsAccessible(container, player) && container.IsOwner())
					{
						list.Add(container);
					}
				}
			}
			Vector3 position = ((Component)player).transform.position;
			list.Sort(delegate(Container left, Container right)
			{
				//IL_0006: 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_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_0027: 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_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)
				Vector3 val = ((Component)left).transform.position - position;
				float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
				val = ((Component)right).transform.position - position;
				int num = sqrMagnitude.CompareTo(((Vector3)(ref val)).sqrMagnitude);
				return (num == 0) ? ((Object)left).GetInstanceID().CompareTo(((Object)right).GetInstanceID()) : num;
			});
			return list;
		}
	}
	internal static class StorageInputTransfer
	{
		internal static bool TryGetInventory(Container container, Player player, out Inventory inventory)
		{
			inventory = null;
			if (!NearbyStorage.IsAccessible(container, player) || !container.IsOwner())
			{
				return false;
			}
			inventory = container.GetInventory();
			return inventory != null;
		}

		internal static bool ConsumeOne(Container container, Inventory inventory, Player player, ItemData item, string stationName, Func<bool> canInsert, Action insert, Func<bool> wasInserted)
		{
			if (!TryGetInventory(container, player, out var inventory2) || inventory2 != inventory || !inventory.ContainsItem(item) || item.m_stack <= 0 || !canInsert())
			{
				CookFromStoragePlugin.Log.LogWarning((object)("Storage or " + stationName + " changed before transfer; no ingredient was added."));
				return false;
			}
			int stack = item.m_stack;
			int originalCount = CountItems(inventory, item.m_shared.m_name);
			container.m_inUse = true;
			try
			{
				if (!inventory.RemoveItem(item, 1))
				{
					CookFromStoragePlugin.Log.LogWarning((object)"Storage ingredient changed before transfer; no station input was added.");
					return false;
				}
				Exception ex = null;
				try
				{
					insert();
				}
				catch (Exception ex2)
				{
					ex = ex2;
				}
				bool flag;
				try
				{
					flag = wasInserted();
				}
				catch (Exception arg)
				{
					CookFromStoragePlugin.Log.LogError((object)$"Could not confirm {stationName} transfer; storage ingredient remains consumed to avoid duplicate insertion. {arg}");
					return false;
				}
				if (flag)
				{
					if (ex != null)
					{
						CookFromStoragePlugin.Log.LogError((object)$"{stationName} accepted the storage ingredient, but its transfer callback threw after insertion. {ex}");
					}
					return true;
				}
				bool flag2;
				try
				{
					flag2 = RestoreItem(inventory, item, stack, originalCount);
				}
				catch (Exception arg2)
				{
					CookFromStoragePlugin.Log.LogError((object)$"{stationName} rejected an ingredient and storage restoration threw. {arg2}");
					throw;
				}
				CookFromStoragePlugin.Log.LogError((object)(stationName + " rejected a storage ingredient; source restoration " + (flag2 ? "succeeded" : "failed") + "." + ((ex == null) ? string.Empty : $" Transfer callback error: {ex}")));
				return false;
			}
			finally
			{
				container.m_inUse = false;
			}
		}

		private static bool RestoreItem(Inventory inventory, ItemData item, int originalStack, int originalCount)
		{
			item.m_stack = originalStack;
			if (!inventory.ContainsItem(item) && !inventory.AddItem(item))
			{
				return false;
			}
			return CountItems(inventory, item.m_shared.m_name) >= originalCount;
		}

		private static int CountItems(Inventory inventory, string itemName)
		{
			int num = 0;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem?.m_shared != null && allItem.m_shared.m_name == itemName)
				{
					num = checked(num + allItem.m_stack);
				}
			}
			return num;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}

plugins/ValheimMods.Common.dll

Decompiled an hour ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: AssemblyCompany("Hoose")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) Hoose 2026")]
[assembly: AssemblyDescription("Shared runtime helpers for Hoose's Valheim mods.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+896f20ee711cb5c9c1e236c5fb2138f9b73eb0d1")]
[assembly: AssemblyProduct("ValheimMods.Common")]
[assembly: AssemblyTitle("ValheimMods.Common")]
[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 ValheimMods.Common
{
	public sealed class ContainerRegistry
	{
		private readonly HashSet<Container> _containers = new HashSet<Container>();

		public IEnumerable<Container> Containers => _containers;

		public void Initialize()
		{
			Clear();
			Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
			foreach (Container container in array)
			{
				Add(container);
			}
		}

		public void Add(Container container)
		{
			if ((Object)(object)container != (Object)null)
			{
				_containers.Add(container);
			}
		}

		public void Remove(Container container)
		{
			_containers.Remove(container);
		}

		public void Clear()
		{
			_containers.Clear();
		}

		public void PruneDestroyed(Action<Container> onDestroyed = null)
		{
			_containers.RemoveWhere(delegate(Container container)
			{
				if ((Object)(object)container != (Object)null)
				{
					return false;
				}
				onDestroyed?.Invoke(container);
				return true;
			});
		}

		public static bool IsAccessible(Container container, Player player, float radius, bool allowReserved = false)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)container != (Object)null && ((Behaviour)container).isActiveAndEnabled && (Object)(object)player != (Object)null && (Object)(object)container.m_nview != (Object)null && container.m_nview.IsValid() && container.GetInventory() != null)
			{
				Vector3 val = ((Component)container).transform.position - ((Component)player).transform.position;
				if (((Vector3)(ref val)).sqrMagnitude <= radius * radius && (allowReserved || !container.IsInUse()) && (container.IsOwner() || container.m_nview.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 0) && ((Object)(object)container.m_wagon == (Object)null || !container.m_wagon.InUse()) && container.CheckAccess(player.GetPlayerID()))
				{
					if (container.m_checkGuardStone)
					{
						return PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false);
					}
					return true;
				}
			}
			return false;
		}
	}
	public static class StorageRange
	{
		public const float DefaultRadius = 20f;

		public const float MinimumRadius = 1f;

		public const float MaximumRadius = 100f;

		public const float StationReach = 20f;

		public static float Clamp(float radius)
		{
			if (!float.IsNaN(radius))
			{
				return Math.Min(100f, Math.Max(1f, radius));
			}
			return 20f;
		}
	}
}
namespace ValheimMods.Common.Crafting
{
	[HarmonyPatch(typeof(Player), "GetFirstRequiredItem")]
	internal static class AlternativeIngredientPatch
	{
		[HarmonyPrefix]
		private static bool BeforeSelection(Player __instance, Inventory inventory, Recipe recipe, int qualityLevel, ref int amount, ref int extraAmount, int craftMultiplier, ref ItemData __result)
		{
			using CraftingContext craftingContext = CraftingContext.Enter(__instance, recipe);
			if (craftingContext == null || inventory != ((Humanoid)__instance).GetInventory() || !recipe.m_requireOnlyOneIngredient)
			{
				return true;
			}
			__result = craftingContext.FirstIngredient(recipe, qualityLevel, craftMultiplier, out amount, out extraAmount);
			return false;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "OnCraftCancelPressed")]
	internal static class CancelCraftPatch
	{
		[HarmonyPrefix]
		private static void BeforeCancel()
		{
			StorageCrafting.Cancel();
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class ContainerCreatedPatch
	{
		[HarmonyPostfix]
		private static void AfterAwake(Container __instance)
		{
			NearbyStorage.Add(__instance);
		}
	}
	[HarmonyPatch(typeof(Container), "OnDestroyed")]
	internal static class ContainerDestroyedPatch
	{
		[HarmonyPrefix]
		private static void BeforeDestroy(Container __instance)
		{
			NearbyStorage.Remove(__instance);
		}
	}
	internal sealed class CraftingContext : IDisposable
	{
		private readonly CraftingContext _previous;

		internal static CraftingContext Current { get; private set; }

		internal Player LocalPlayer { get; }

		internal List<Container> Containers { get; }

		internal IngredientPlan Plan { get; set; }

		internal bool Reserved { get; set; }

		internal bool IncludeStorageCounts { get; set; }

		internal CraftingContext(Player player, List<Container> containers)
		{
			LocalPlayer = player;
			Containers = containers;
			_previous = Current;
			Current = this;
		}

		public void Dispose()
		{
			Current = _previous;
		}

		internal static bool IsStationRecipe(Player player, Recipe recipe)
		{
			if ((Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && (Object)(object)recipe != (Object)null && (Object)(object)recipe.m_craftingStation != (Object)null && (Object)(object)player.GetCurrentCraftingStation() != (Object)null)
			{
				return RecipeStorage.Supports(player, recipe);
			}
			return false;
		}

		internal static CraftingContext Enter(Player player, Recipe recipe)
		{
			if (!IsStationRecipe(player, recipe))
			{
				return null;
			}
			CraftingContext current = Current;
			List<Container> containers = (((Object)(object)current?.LocalPlayer == (Object)(object)player) ? current.Containers : NearbyStorage.Find(player));
			return new CraftingContext(player, containers)
			{
				Reserved = (current?.Reserved ?? false),
				Plan = current?.Plan,
				IncludeStorageCounts = true
			};
		}

		internal static bool Matches(ItemData item, string name, int quality, bool worldLevel)
		{
			if (item.m_stack > 0 && item.m_shared.m_name == name && (quality < 0 || item.m_quality == quality))
			{
				if (worldLevel)
				{
					return item.m_worldLevel >= Game.m_worldLevel;
				}
				return true;
			}
			return false;
		}

		internal IEnumerable<Inventory> Inventories()
		{
			yield return ((Humanoid)LocalPlayer).GetInventory();
			foreach (Container container in Containers)
			{
				if (NearbyStorage.IsAccessible(container, LocalPlayer, Reserved))
				{
					yield return container.GetInventory();
				}
			}
		}

		internal int CountStorage(string name, int quality, bool worldLevel)
		{
			int num = 0;
			foreach (Inventory item in Inventories())
			{
				if (item == ((Humanoid)LocalPlayer).GetInventory())
				{
					continue;
				}
				foreach (ItemData allItem in item.GetAllItems())
				{
					if (Matches(allItem, name, quality, worldLevel))
					{
						num = checked(num + allItem.m_stack);
					}
				}
			}
			return num;
		}

		internal ItemData FirstIngredient(Recipe recipe, int qualityLevel, int multiplier, out int amount, out int extraAmount)
		{
			CraftingStation currentCraftingStation = LocalPlayer.GetCurrentCraftingStation();
			Requirement[] resources = recipe.m_resources;
			foreach (Requirement val in resources)
			{
				if ((Object)(object)val.m_resItem == (Object)null || currentCraftingStation.m_upgrader != val.m_upgraderResource)
				{
					continue;
				}
				int num = checked(val.GetAmount(qualityLevel) * multiplier);
				if (num <= 0)
				{
					continue;
				}
				string name = val.m_resItem.m_itemData.m_shared.m_name;
				for (int j = 0; j <= val.m_resItem.m_itemData.m_shared.m_maxQuality; j++)
				{
					int num2 = 0;
					ItemData val2 = null;
					foreach (Inventory item in Inventories())
					{
						foreach (ItemData allItem in item.GetAllItems())
						{
							if (Matches(allItem, name, j, worldLevel: true))
							{
								if (val2 == null)
								{
									val2 = allItem;
								}
								num2 = checked(num2 + allItem.m_stack);
							}
						}
					}
					if (num2 >= num)
					{
						amount = num;
						extraAmount = val.m_extraAmountOnlyOneIngredient;
						return val2;
					}
				}
			}
			amount = 0;
			extraAmount = 0;
			return null;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	internal static class CraftTransactionPatch
	{
		[HarmonyPrefix]
		private static bool BeforeCraft(InventoryGui __instance, Player player)
		{
			if (!CraftingContext.IsStationRecipe(player, __instance.m_craftRecipe) || StorageCrafting.IsExecuting)
			{
				return true;
			}
			if (StorageCrafting.IsPending)
			{
				return false;
			}
			if (player.NoCostCheat() || ZoneSystem.instance.GetGlobalKey((GlobalKeys)25))
			{
				return true;
			}
			RecipeStorage.StartCraft(__instance, player);
			return false;
		}

		[HarmonyFinalizer]
		private static void AfterCraft(Exception __exception)
		{
			if (__exception != null)
			{
				RecipeStorage.LogError($"Crafting failed: {__exception}");
			}
		}
	}
	internal sealed class IngredientPlan
	{
		private sealed class Portion
		{
			internal Inventory Inventory { get; set; }

			internal ItemData Item { get; set; }

			internal int Amount { get; set; }
		}

		private sealed class Ingredient
		{
			internal string Name { get; set; }

			internal int Quality { get; set; }

			internal int Amount { get; set; }

			internal bool Consumed { get; set; }

			internal List<Portion> Portions { get; } = new List<Portion>();
		}

		private readonly List<Ingredient> _ingredients = new List<Ingredient>();

		internal static IngredientPlan Create(CraftingContext context, Recipe recipe, int quality, int multiplier)
		{
			IngredientPlan ingredientPlan = new IngredientPlan();
			if (recipe.m_requireOnlyOneIngredient)
			{
				int amount;
				int extraAmount;
				ItemData val = context.FirstIngredient(recipe, quality, multiplier, out amount, out extraAmount);
				if (val == null)
				{
					return null;
				}
				ingredientPlan._ingredients.Add(new Ingredient
				{
					Name = val.m_shared.m_name,
					Quality = val.m_quality,
					Amount = amount
				});
			}
			else
			{
				CraftingStation currentCraftingStation = context.LocalPlayer.GetCurrentCraftingStation();
				Requirement[] resources = recipe.m_resources;
				foreach (Requirement val2 in resources)
				{
					if (!((Object)(object)val2.m_resItem == (Object)null) && currentCraftingStation.m_upgrader == val2.m_upgraderResource)
					{
						int num = checked(val2.GetAmount(quality) * multiplier);
						if (num > 0)
						{
							ingredientPlan._ingredients.Add(new Ingredient
							{
								Name = val2.m_resItem.m_itemData.m_shared.m_name,
								Quality = -1,
								Amount = num
							});
						}
					}
				}
			}
			Dictionary<ItemData, int> dictionary = new Dictionary<ItemData, int>();
			foreach (Ingredient ingredient in ingredientPlan._ingredients)
			{
				int num2 = ingredient.Amount;
				foreach (Inventory item in context.Inventories())
				{
					foreach (ItemData allItem in item.GetAllItems())
					{
						if (CraftingContext.Matches(allItem, ingredient.Name, ingredient.Quality, worldLevel: true))
						{
							dictionary.TryGetValue(allItem, out var value);
							int num3 = Math.Min(num2, allItem.m_stack - value);
							if (num3 > 0)
							{
								ingredient.Portions.Add(new Portion
								{
									Inventory = item,
									Item = allItem,
									Amount = num3
								});
								dictionary[allItem] = value + num3;
								num2 -= num3;
							}
						}
					}
				}
				if (num2 > 0)
				{
					return null;
				}
			}
			return ingredientPlan;
		}

		internal List<Container> StorageSources(CraftingContext context)
		{
			HashSet<Inventory> inventories = new HashSet<Inventory>();
			foreach (Ingredient ingredient in _ingredients)
			{
				foreach (Portion portion in ingredient.Portions)
				{
					inventories.Add(portion.Inventory);
				}
			}
			return context.Containers.FindAll((Container container) => inventories.Contains(container.GetInventory()));
		}

		internal void Consume(string name, int amount, int quality, bool worldLevel)
		{
			Ingredient ingredient = _ingredients.Find((Ingredient entry) => !entry.Consumed && entry.Name == name && entry.Amount == amount && entry.Quality == quality);
			if (ingredient == null || !worldLevel)
			{
				throw new InvalidOperationException($"Unexpected crafting consumption: {name}, amount {amount}, quality {quality}.");
			}
			foreach (Portion portion in ingredient.Portions)
			{
				if (!portion.Inventory.ContainsItem(portion.Item) || portion.Item.m_stack < portion.Amount)
				{
					throw new InvalidOperationException("Reserved crafting ingredient changed: " + name + ".");
				}
			}
			foreach (Portion portion2 in ingredient.Portions)
			{
				if (!portion2.Inventory.RemoveItem(portion2.Item, portion2.Amount))
				{
					throw new InvalidOperationException("Could not consume reserved crafting ingredient: " + name + ".");
				}
			}
			ingredient.Consumed = true;
		}
	}
	[HarmonyPatch(typeof(Inventory), "RemoveItem", new Type[]
	{
		typeof(string),
		typeof(int),
		typeof(int),
		typeof(bool)
	})]
	internal static class InventoryConsumePatch
	{
		[HarmonyPrefix]
		private static bool BeforeRemove(Inventory __instance, string name, int amount, int itemQuality, bool worldLevelBased)
		{
			CraftingContext current = CraftingContext.Current;
			if (!StorageCrafting.IsExecuting || current?.Plan == null || __instance != ((Humanoid)current.LocalPlayer).GetInventory())
			{
				return true;
			}
			current.Plan.Consume(name, amount, itemQuality, worldLevelBased);
			return false;
		}
	}
	[HarmonyPatch(typeof(Inventory), "CountItems", new Type[]
	{
		typeof(string),
		typeof(int),
		typeof(bool)
	})]
	internal static class InventoryCountPatch
	{
		[HarmonyPostfix]
		private static void AfterCount(Inventory __instance, string name, int quality, bool matchWorldLevel, ref int __result)
		{
			CraftingContext current = CraftingContext.Current;
			checked
			{
				if (current != null && current.IncludeStorageCounts && __instance == ((Humanoid)current.LocalPlayer).GetInventory())
				{
					__result += current.CountStorage(name, quality, matchWorldLevel);
				}
			}
		}
	}
	internal static class NearbyStorage
	{
		private static readonly ContainerRegistry _registry = new ContainerRegistry();

		private static List<Container> _cached;

		private static int _frame = -1;

		private static Player _player;

		private static Vector3 _position;

		private static float _radius;

		internal static void Initialize()
		{
			Clear();
			_registry.Initialize();
		}

		internal static void Add(Container container)
		{
			_registry.Add(container);
			_frame = -1;
		}

		internal static void Remove(Container container)
		{
			_registry.Remove(container);
			StorageCrafting.Forget(container);
			_frame = -1;
		}

		internal static void Clear()
		{
			_registry.Clear();
			_cached = null;
			_player = null;
			_frame = -1;
		}

		internal static bool IsAccessible(Container container, Player player, bool allowReserved = false)
		{
			return ContainerRegistry.IsAccessible(container, player, RecipeStorage.RadiusFor(player), allowReserved);
		}

		internal static List<Container> Find(Player player, bool refresh = false)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: 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_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			float num = RecipeStorage.RadiusFor(player);
			if (!refresh && _cached != null && _frame == Time.frameCount && (Object)(object)_player == (Object)(object)player && _position == ((Component)player).transform.position && _radius == num)
			{
				return _cached;
			}
			List<Container> list = new List<Container>();
			_registry.PruneDestroyed(StorageCrafting.Forget);
			foreach (Container container in _registry.Containers)
			{
				if (ContainerRegistry.IsAccessible(container, player, num))
				{
					container.Load();
					list.Add(container);
				}
			}
			Vector3 position = ((Component)player).transform.position;
			list.Sort(delegate(Container left, Container right)
			{
				//IL_0006: 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_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_0027: 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_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)
				Vector3 val = ((Component)left).transform.position - position;
				float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
				val = ((Component)right).transform.position - position;
				int num2 = sqrMagnitude.CompareTo(((Vector3)(ref val)).sqrMagnitude);
				return (num2 == 0) ? ((Object)left).GetInstanceID().CompareTo(((Object)right).GetInstanceID()) : num2;
			});
			_cached = list;
			_player = player;
			_position = position;
			_radius = num;
			_frame = Time.frameCount;
			return list;
		}
	}
	[HarmonyPatch(typeof(Container), "Interact")]
	internal static class PendingContainerInteractionPatch
	{
		[HarmonyPrefix]
		private static bool BeforeInteract(Container __instance, Humanoid character, bool hold, ref bool __result)
		{
			if (!StorageCrafting.IsBusy(__instance))
			{
				StorageCrafting.PrepareManualOpen(__instance, character, hold);
				return true;
			}
			((Character)character).Message((MessageType)2, "$msg_inuse", 0, (Sprite)null, false);
			__result = true;
			return false;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")]
	internal static class PendingCraftPatch
	{
		[HarmonyPrefix]
		private static bool BeforePress()
		{
			if (!StorageCrafting.IsPending)
			{
				return true;
			}
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				((Character)localPlayer).Message((MessageType)2, "$msg_inuse", 0, (Sprite)null, false);
			}
			return false;
		}
	}
	public sealed class RecipeStorage : IDisposable
	{
		private const string HarmonyOwner = "hoose.valheim.sharedrecipestorage";

		private static readonly Dictionary<bool, RecipeStorage> _clients = new Dictionary<bool, RecipeStorage>();

		private static Harmony _harmony;

		private static RecipeStorage _active;

		private readonly Action<IEnumerator> _startCoroutine;

		private readonly Action _stopCoroutines;

		private readonly Action<string> _warning;

		private readonly Action<string> _error;

		private readonly Func<bool> _enabled;

		private readonly Func<float> _radius;

		private readonly bool _food;

		private float Radius => StorageRange.Clamp(_radius?.Invoke() ?? 20f);

		private RecipeStorage(bool food, Action<IEnumerator> startCoroutine, Action stopCoroutines, Action<string> warning, Action<string> error, Func<bool> enabled, Func<float> radius)
		{
			_food = food;
			_startCoroutine = startCoroutine;
			_stopCoroutines = stopCoroutines;
			_warning = warning;
			_error = error;
			_enabled = enabled;
			_radius = radius;
		}

		public static RecipeStorage Register(bool food, Action<IEnumerator> startCoroutine, Action stopCoroutines, Action<string> warning, Action<string> error, Func<bool> enabled, Func<float> radius = null)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			if (startCoroutine == null || stopCoroutines == null || warning == null || error == null || enabled == null)
			{
				throw new ArgumentNullException("startCoroutine", "Recipe-storage callbacks must not be null.");
			}
			if (_clients.ContainsKey(food))
			{
				throw new InvalidOperationException("A recipe-storage client is already registered for this station category.");
			}
			if (_clients.Count == 0)
			{
				Harmony val = new Harmony("hoose.valheim.sharedrecipestorage");
				try
				{
					Type[] array = (from type2 in typeof(RecipeStorage).Assembly.GetTypes()
						where type2.Namespace == typeof(RecipeStorage).Namespace && type2.IsDefined(typeof(HarmonyPatch), inherit: false)
						select type2).ToArray();
					Type[] array2 = array;
					foreach (Type type in array2)
					{
						val.CreateClassProcessor(type).Patch();
					}
					IEnumerable<MethodInfo> enumerable = from method in array.SelectMany((Type type2) => type2.GetMethods(BindingFlags.Static | BindingFlags.NonPublic))
						where method.IsDefined(typeof(HarmonyPrefix), inherit: false) || method.IsDefined(typeof(HarmonyPostfix), inherit: false) || method.IsDefined(typeof(HarmonyFinalizer), inherit: false)
						select method;
					HashSet<MethodInfo> hashSet = (from patch in val.GetPatchedMethods().Select((Func<MethodBase, Patches>)Harmony.GetPatchInfo).SelectMany((Patches info) => info.Prefixes.Concat(info.Postfixes).Concat(info.Finalizers))
						where patch.owner == "hoose.valheim.sharedrecipestorage"
						select patch.PatchMethod).ToHashSet();
					foreach (MethodInfo item in enumerable)
					{
						if (!hashSet.Contains(item))
						{
							throw new InvalidOperationException("Shared recipe patch was not registered: " + item.DeclaringType.Name + "." + item.Name + ".");
						}
					}
					NearbyStorage.Initialize();
					_harmony = val;
				}
				catch
				{
					val.UnpatchSelf();
					NearbyStorage.Clear();
					throw;
				}
			}
			RecipeStorage recipeStorage = new RecipeStorage(food, startCoroutine, stopCoroutines, warning, error, enabled, radius);
			_clients.Add(food, recipeStorage);
			return recipeStorage;
		}

		public static bool IsFoodStation(CraftingStation station)
		{
			bool flag = (Object)(object)station != (Object)null;
			if (flag)
			{
				bool flag2;
				switch (station.m_name)
				{
				case "$piece_cauldron":
				case "$piece_preptable":
				case "$piece_meadcauldron":
					flag2 = true;
					break;
				default:
					flag2 = false;
					break;
				}
				flag = flag2;
			}
			return flag;
		}

		public void Dispose()
		{
			if (_clients.TryGetValue(_food, out var value) && value == this)
			{
				if (_active == this)
				{
					StorageCrafting.Cancel();
					_stopCoroutines();
					_active = null;
				}
				_clients.Remove(_food);
				if (_clients.Count == 0)
				{
					StorageCrafting.Clear();
					NearbyStorage.Clear();
					_harmony.UnpatchSelf();
					_harmony = null;
				}
			}
		}

		internal static bool Supports(Player player, Recipe recipe)
		{
			CraftingStation currentCraftingStation = player.GetCurrentCraftingStation();
			if (_clients.TryGetValue(IsFoodStation(recipe.m_craftingStation), out var value) && value._enabled())
			{
				return IsFoodStation(currentCraftingStation) == value._food;
			}
			return false;
		}

		internal static void StartCraft(InventoryGui gui, Player player)
		{
			_active = _clients[IsFoodStation(gui.m_craftRecipe.m_craftingStation)];
			try
			{
				_active._startCoroutine(StorageCrafting.Craft(gui, player));
			}
			finally
			{
				if (!StorageCrafting.IsPending)
				{
					_active = null;
				}
			}
		}

		internal static void FinishCraft()
		{
			_active = null;
		}

		internal static float RadiusFor(Player player)
		{
			if (_active != null)
			{
				return _active.Radius;
			}
			CraftingStation val = ((player != null) ? player.GetCurrentCraftingStation() : null);
			if ((Object)(object)val != (Object)null && _clients.TryGetValue(IsFoodStation(val), out var value))
			{
				return value.Radius;
			}
			if (_clients.Count != 0)
			{
				return _clients.Values.Max((RecipeStorage registered) => registered.Radius);
			}
			return 20f;
		}

		internal static void LogWarning(string message)
		{
			Log(message, error: false);
		}

		internal static void LogError(string message)
		{
			Log(message, error: true);
		}

		private static void Log(string message, bool error)
		{
			RecipeStorage recipeStorage = _active ?? _clients.Values.First();
			(error ? recipeStorage._error : recipeStorage._warning)(message);
		}
	}
	[HarmonyPatch(typeof(Player), "HaveRequirementItems")]
	internal static class RequirementCheckPatch
	{
		[HarmonyPrefix]
		private static void BeforeCheck(Player __instance, Recipe __0, bool discover, out CraftingContext __state)
		{
			__state = (discover ? null : CraftingContext.Enter(__instance, __0));
		}

		[HarmonyFinalizer]
		private static void AfterCheck(CraftingContext __state)
		{
			__state?.Dispose();
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "SetupRequirement")]
	internal static class RequirementDisplayPatch
	{
		[HarmonyPrefix]
		private static void BeforeDisplay(Player player, bool craft, out CraftingContext __state)
		{
			InventoryGui instance = InventoryGui.instance;
			__state = ((!craft || (Object)(object)instance == (Object)null || !InventoryGui.IsVisible()) ? null : CraftingContext.Enter(player, ((RecipeDataPair)(ref instance.m_selectedRecipe)).Recipe));
		}

		[HarmonyFinalizer]
		private static void AfterDisplay(CraftingContext __state)
		{
			__state?.Dispose();
		}
	}
	internal static class StorageCrafting
	{
		private sealed class Request
		{
			internal bool Responded { get; set; }

			internal bool Granted { get; set; }
		}

		private sealed class ManualOpen
		{
			internal Player Player { get; set; }

			internal float Deadline { get; set; }
		}

		private const float OwnershipTimeout = 5f;

		private static readonly Dictionary<Container, Request> _requests = new Dictionary<Container, Request>();

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

		private static readonly Dictionary<Container, ManualOpen> _manualOpens = new Dictionary<Container, ManualOpen>();

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

		private static bool _cancelled;

		internal static bool IsPending { get; private set; }

		internal static bool IsExecuting { get; private set; }

		internal static bool HasRequest(Container container)
		{
			return _requests.ContainsKey(container);
		}

		internal static bool IsBusy(Container container)
		{
			if (_manualOpens.TryGetValue(container, out var value) && Time.realtimeSinceStartup >= value.Deadline)
			{
				_manualOpens.Remove(container);
			}
			if (!HasRequest(container) && !_reserved.Contains(container))
			{
				return _manualOpens.ContainsKey(container);
			}
			return true;
		}

		internal static void PrepareManualOpen(Container container, Humanoid character, bool hold)
		{
			Player localPlayer = Player.m_localPlayer;
			if (!hold && (Object)(object)character == (Object)(object)localPlayer && _responseGuards.Contains(container) && NearbyStorage.IsAccessible(container, localPlayer))
			{
				_manualOpens[container] = new ManualOpen
				{
					Player = localPlayer,
					Deadline = Time.realtimeSinceStartup + 5f
				};
			}
		}

		internal static void Forget(Container container)
		{
			_requests.Remove(container);
			_manualOpens.Remove(container);
			_responseGuards.Remove(container);
		}

		internal static bool ReceiveResponse(Container container, bool granted)
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			if (_requests.TryGetValue(container, out var value))
			{
				value.Responded = true;
				value.Granted = granted;
				return true;
			}
			if (_manualOpens.TryGetValue(container, out var value2))
			{
				_manualOpens.Remove(container);
				Player player = value2.Player;
				if (Time.realtimeSinceStartup < value2.Deadline && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead() && NearbyStorage.IsAccessible(container, player))
				{
					Vector3 val = ((Component)container).transform.position - ((Component)player).transform.position;
					if (((Vector3)(ref val)).sqrMagnitude <= player.m_maxInteractDistance * player.m_maxInteractDistance && (!granted || container.IsOwner()))
					{
						return false;
					}
				}
				Failed(player, "Storage response arrived after manual access became invalid; opening cancelled.");
			}
			return _responseGuards.Contains(container);
		}

		internal static void Cancel()
		{
			_cancelled = true;
			Release();
		}

		internal static void Clear()
		{
			Cancel();
			_requests.Clear();
			_manualOpens.Clear();
			_responseGuards.Clear();
			IsExecuting = false;
			IsPending = false;
		}

		internal static IEnumerator Craft(InventoryGui gui, Player player)
		{
			IsPending = true;
			_cancelled = false;
			Recipe recipe = gui.m_craftRecipe;
			ItemData upgrade = gui.m_craftUpgradeItem;
			CraftingStation station = player.GetCurrentCraftingStation();
			int quality = ((upgrade == null) ? 1 : (upgrade.m_quality + 1));
			int multiplier = ((!gui.m_multiCrafting) ? 1 : gui.m_multiCraftAmount);
			try
			{
				List<Container> sources;
				using (CraftingContext context = new CraftingContext(player, NearbyStorage.Find(player, refresh: true)))
				{
					IngredientPlan ingredientPlan = IngredientPlan.Create(context, recipe, quality, multiplier);
					if (ingredientPlan == null)
					{
						Failed(player, "Insufficient ingredients when crafting completed; no ingredients were consumed.");
						yield break;
					}
					sources = ingredientPlan.StorageSources(context);
				}
				foreach (Container container in sources)
				{
					if (!Valid(gui, player, recipe, upgrade, station))
					{
						yield break;
					}
					if (!NearbyStorage.IsAccessible(container, player))
					{
						Failed(player, "Storage changed before crafting; no ingredients were consumed.");
						yield break;
					}
					if (IsBusy(container))
					{
						Failed(player, "Storage has another interaction pending; crafting cancelled.");
						yield break;
					}
					if (!container.IsOwner())
					{
						Request request = new Request();
						_requests.Add(container, request);
						_responseGuards.Add(container);
						container.m_nview.InvokeRPC("RPC_RequestOpen", new object[1] { player.GetPlayerID() });
						float deadline = Time.realtimeSinceStartup + 5f;
						while (true)
						{
							if (!Valid(gui, player, recipe, upgrade, station) || (Object)(object)container == (Object)null || (Object)(object)container.m_nview == (Object)null || !container.m_nview.IsValid())
							{
								Failed(player, "Crafting or storage became invalid while awaiting ownership; crafting cancelled.");
								yield break;
							}
							if ((request.Responded && !request.Granted) || Time.realtimeSinceStartup >= deadline)
							{
								Failed(player, "Storage ownership was denied or timed out; no ingredients were consumed.");
								yield break;
							}
							if (request.Responded && container.IsOwner())
							{
								break;
							}
							yield return null;
						}
						_requests.Remove(container);
					}
					if (!NearbyStorage.IsAccessible(container, player))
					{
						Failed(player, "Storage became inaccessible while awaiting ownership; crafting cancelled.");
						yield break;
					}
					container.Load();
					container.m_inUse = true;
					_reserved.Add(container);
				}
				if (!Valid(gui, player, recipe, upgrade, station))
				{
					yield break;
				}
				foreach (Container item in sources)
				{
					if (!NearbyStorage.IsAccessible(item, player, allowReserved: true) || !item.IsOwner())
					{
						Failed(player, "Reserved storage is no longer owned or accessible; crafting cancelled.");
						yield break;
					}
				}
				using CraftingContext craftingContext = new CraftingContext(player, sources)
				{
					Reserved = true
				};
				craftingContext.Plan = IngredientPlan.Create(craftingContext, recipe, quality, multiplier);
				if (craftingContext.Plan == null)
				{
					Failed(player, "Insufficient ingredients after refreshing storage; no ingredients were consumed.");
					yield break;
				}
				IsExecuting = true;
				gui.m_multiCraftAmount = multiplier;
				gui.DoCrafting(player);
			}
			finally
			{
				IsExecuting = false;
				IsPending = false;
				_requests.Clear();
				Release();
				RecipeStorage.FinishCraft();
			}
		}

		private static void Release()
		{
			foreach (Container item in _reserved)
			{
				if ((Object)(object)item != (Object)null)
				{
					item.m_inUse = false;
				}
			}
			_reserved.Clear();
		}

		private static bool Valid(InventoryGui gui, Player player, Recipe recipe, ItemData upgrade, CraftingStation station)
		{
			if (!_cancelled && (Object)(object)gui != (Object)null && InventoryGui.IsVisible() && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead() && (Object)(object)gui.m_craftRecipe == (Object)(object)recipe && gui.m_craftUpgradeItem == upgrade && (Object)(object)((RecipeDataPair)(ref gui.m_selectedRecipe)).Recipe == (Object)(object)recipe && ((RecipeDataPair)(ref gui.m_selectedRecipe)).ItemData == upgrade && (Object)(object)player.GetCurrentCraftingStation() == (Object)(object)station)
			{
				return CraftingContext.IsStationRecipe(player, recipe);
			}
			return false;
		}

		private static void Failed(Player player, string reason)
		{
			RecipeStorage.LogWarning(reason);
			if ((Object)(object)player != (Object)null)
			{
				((Character)player).Message((MessageType)2, "$msg_missingrequirement", 0, (Sprite)null, false);
			}
		}
	}
	[HarmonyPatch(typeof(Container), "RPC_OpenResponse")]
	internal static class StorageOwnershipResponsePatch
	{
		[HarmonyPrefix]
		private static bool BeforeResponse(Container __instance, bool granted)
		{
			return !StorageCrafting.ReceiveResponse(__instance, granted);
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}