Decompiled source of CraftFromStorage v1.0.0

plugins/CraftFromStorage.dll

Decompiled an hour ago
using System;
using System.Collections;
using System.Diagnostics;
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 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("Crafts and upgrades at stations using accessible storage near the player (20 m by default, configurable).")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+896f20ee711cb5c9c1e236c5fb2138f9b73eb0d1")]
[assembly: AssemblyProduct("Valheim CraftFromStorage")]
[assembly: AssemblyTitle("CraftFromStorage")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CraftFromStorage
{
	[BepInPlugin("hoose.valheim.craftfromstorage", "CraftFromStorage", "1.0.0")]
	[BepInProcess("valheim.exe")]
	public sealed class CraftFromStoragePlugin : BaseUnityPlugin
	{
		public const string PluginName = "CraftFromStorage";

		public const string PluginAuthor = "Hoose";

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

		public const string PluginVersion = "1.0.0";

		private const string StorageRadiusDescription = "How far, in meters, CraftFromStorage searches for storage around you (a sphere centred on your character). Allowed range: 1 to 100.\nApplies to recipes and upgrades at non-food crafting stations such as the Workbench, Forge, Stonecutter, Artisan Table, Black Forge and Galdr Table.\nCauldron, Food Preparation Table and Mead Ketill recipes are handled by CookFromStorage and use its own setting. Only storage you could normally open is used.";

		private static ConfigEntry<float> _storageRadius;

		private RecipeStorage _recipeStorage;

		internal static CraftFromStoragePlugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal static float StorageRadius => StorageRange.Clamp(_storageRadius?.Value ?? 20f);

		private void Awake()
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			_storageRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Storage", "Storage radius", 20f, new ConfigDescription("How far, in meters, CraftFromStorage searches for storage around you (a sphere centred on your character). Allowed range: 1 to 100.\nApplies to recipes and upgrades at non-food crafting stations such as the Workbench, Forge, Stonecutter, Artisan Table, Black Forge and Galdr Table.\nCauldron, Food Preparation Table and Mead Ketill recipes are handled by CookFromStorage and use its own setting. Only storage you could normally open is used.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>()));
			try
			{
				_recipeStorage = RecipeStorage.Register(false, (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>)(() => true), (Func<float>)(() => StorageRadius));
			}
			catch (Exception arg)
			{
				Log.LogError((object)string.Format("{0}: shared recipe-storage initialization failed. {1}", "CraftFromStorage", arg));
				throw;
			}
			Log.LogInfo((object)string.Format("{0} {1} loaded; non-food station crafting patches verified ({2:0.#}m storage radius).", "CraftFromStorage", "1.0.0", StorageRadius));
		}

		private void OnDestroy()
		{
			RecipeStorage recipeStorage = _recipeStorage;
			if (recipeStorage != null)
			{
				recipeStorage.Dispose();
			}
			((MonoBehaviour)this).StopAllCoroutines();
			if (Instance == this)
			{
				Instance = null;
				Log = null;
			}
		}
	}
}
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)
		{
		}
	}
}