Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CookFromStorage v1.0.0
plugins/CookFromStorage.dll
Decompiled an hour agousing 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 agousing 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) { } } }