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 SupplyFromStorage v1.0.0
plugins/SupplyFromStorage.dll
Decompiled an hour agousing System; 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; [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("Supplies one accepted fuel or processing input from nearby storage per machine E interaction.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+896f20ee711cb5c9c1e236c5fb2138f9b73eb0d1")] [assembly: AssemblyProduct("Valheim SupplyFromStorage")] [assembly: AssemblyTitle("SupplyFromStorage")] [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 SupplyFromStorage { [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); } } [HarmonyPatch(typeof(CookingStation), "OnAddFuelSwitch")] internal static class CookingStationFuelPatch { [HarmonyPrefix] private static bool BeforeFuelRequested(CookingStation __instance, Humanoid user, ItemData item, ref bool __result) { if (!SupplyTransfer.TryHandleCookingStationFuel(__instance, user, item)) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(Fireplace), "Interact")] internal static class FireplaceInteractPatch { [HarmonyPrefix] private static void BeforeInteract(Fireplace __instance, bool hold, out bool __state) { __state = !hold || Time.time - __instance.m_lastUseTime >= __instance.m_holdRepeatInterval; } [HarmonyPostfix] private static void AfterInteract(Fireplace __instance, Humanoid user, bool alt, bool __result, bool __state) { SupplyTransfer.FireplaceInteracted(__instance, user, __state && !alt, __result); } } internal static class NearbyStorage { private static readonly ContainerRegistry _registry = new ContainerRegistry(); internal static float Radius => StorageRange.Clamp(SupplyFromStoragePlugin.StorageRadius); internal static void Initialize() { _registry.Initialize(); } internal static void Add(Container container) { if ((Object)(object)container != (Object)null) { _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; } } [HarmonyPatch(typeof(Smelter), "OnAddFuel")] internal static class SmelterFuelPatch { [HarmonyPrefix] private static bool BeforeFuelRequested(Smelter __instance, Humanoid user, ItemData item, ref bool __result) { if (!SupplyTransfer.TryHandleSmelterFuel(__instance, user, item)) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(Smelter), "OnAddOre")] internal static class SmelterInputPatch { [HarmonyPrefix] private static bool BeforeInputRequested(Smelter __instance, Humanoid user, ItemData item, ref bool __result) { if (!SupplyTransfer.TryHandleSmelterInput(__instance, user, item, out var handledResult)) { return true; } __result = handledResult; return false; } } [BepInPlugin("hoose.valheim.supplyfromstorage", "SupplyFromStorage", "1.0.0")] [BepInProcess("valheim.exe")] public sealed class SupplyFromStoragePlugin : BaseUnityPlugin { public const string PluginName = "SupplyFromStorage"; public const string PluginAuthor = "Hoose"; public const string PluginGuid = "hoose.valheim.supplyfromstorage"; public const string PluginVersion = "1.0.0"; private const string StorageRadiusDescription = "How far, in meters, SupplyFromStorage searches for storage around you (a sphere centred on your character). Allowed range: 1 to 100.\nApplies to both 'Automatic fuel' and 'Automatic processing inputs'. Only storage you could normally open is used."; private const string AutomaticFuelDescription = "When you press E on a machine's fuel slot and have no accepted fuel in your inventory, add one accepted fuel item from accessible storage within 'Storage radius'.\nCovers:\n- Campfires, bonfires, hearths, fireplaces, braziers, torches and other fires.\n- Cooking-station and Stone Oven fuel, and the Frost Foundry's liquid frost. Never cooking ingredients or Foundry casts.\n- Smelter and Blast Furnace fuel (coal).\n- Hot Tub (wood) and Frost Kiln (ice).\nYour inventory is always used first. Disabling this does not affect 'Automatic processing inputs'."; private const string AutomaticProcessingInputsDescription = "When you press E on a machine's input slot and have no accepted input in your inventory, add one accepted item from accessible storage within 'Storage radius'.\nCovers:\n- Ore and scrap for the Smelter and Blast Furnace.\n- Wood for the Charcoal Kiln.\n- Barley for the Windmill and flax for the Spinning Wheel.\n- Eitr Refinery inputs.\n- Wood, Fine Wood, Core Wood or Ashwood for the Battering Ram engine.\nEach machine's own accepted-item list decides what can be added. Your inventory is always used first. Disabling this does not affect 'Automatic fuel'."; private Harmony _harmony; private static ConfigEntry<bool> _automaticFuel; private static ConfigEntry<bool> _automaticProcessingInputs; private static ConfigEntry<float> _storageRadius; internal static ManualLogSource Log { get; private set; } internal static bool FuelAutomationEnabled => _automaticFuel?.Value ?? false; internal static bool ProcessingAutomationEnabled => _automaticProcessingInputs?.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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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, SupplyFromStorage searches for storage around you (a sphere centred on your character). Allowed range: 1 to 100.\nApplies to both 'Automatic fuel' and 'Automatic processing inputs'. Only storage you could normally open is used.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>())); _automaticFuel = ((BaseUnityPlugin)this).Config.Bind<bool>("Automation", "Automatic fuel", true, "When you press E on a machine's fuel slot and have no accepted fuel in your inventory, add one accepted fuel item from accessible storage within 'Storage radius'.\nCovers:\n- Campfires, bonfires, hearths, fireplaces, braziers, torches and other fires.\n- Cooking-station and Stone Oven fuel, and the Frost Foundry's liquid frost. Never cooking ingredients or Foundry casts.\n- Smelter and Blast Furnace fuel (coal).\n- Hot Tub (wood) and Frost Kiln (ice).\nYour inventory is always used first. Disabling this does not affect 'Automatic processing inputs'."); _automaticProcessingInputs = ((BaseUnityPlugin)this).Config.Bind<bool>("Automation", "Automatic processing inputs", true, "When you press E on a machine's input slot and have no accepted input in your inventory, add one accepted item from accessible storage within 'Storage radius'.\nCovers:\n- Ore and scrap for the Smelter and Blast Furnace.\n- Wood for the Charcoal Kiln.\n- Barley for the Windmill and flax for the Spinning Wheel.\n- Eitr Refinery inputs.\n- Wood, Fine Wood, Core Wood or Ashwood for the Battering Ram engine.\nEach machine's own accepted-item list decides what can be added. Your inventory is always used first. Disabling this does not affect 'Automatic fuel'."); NearbyStorage.Initialize(); SupplyTransfer.Clear(); _harmony = new Harmony("hoose.valheim.supplyfromstorage"); _harmony.PatchAll(typeof(SupplyFromStoragePlugin).Assembly); if (!IsRegistered(typeof(Fireplace), "Interact", typeof(FireplaceInteractPatch)) || !IsRegistered(typeof(CookingStation), "OnAddFuelSwitch", typeof(CookingStationFuelPatch)) || !IsRegistered(typeof(Smelter), "OnAddFuel", typeof(SmelterFuelPatch)) || !IsRegistered(typeof(Smelter), "OnAddOre", typeof(SmelterInputPatch)) || !IsRegistered(typeof(Container), "Awake", typeof(ContainerCreatedPatch)) || !IsRegistered(typeof(Container), "OnDestroyed", typeof(ContainerDestroyedPatch))) { _harmony.UnpatchSelf(); Log.LogError((object)"SupplyFromStorage: one or more station/storage patches were not registered; automation is disabled."); } else { Log.LogInfo((object)string.Format("{0} {1} loaded; E interactions add one accepted fuel or processing input from nearby accessible storage ({2:0.#}m).", "SupplyFromStorage", "1.0.0", NearbyStorage.Radius)); } } private void OnDestroy() { SupplyTransfer.Clear(); 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.supplyfromstorage" && patch.PatchMethod.DeclaringType == patchType) ?? false; } } internal static class SupplyTransfer { private sealed class AcceptedInput { internal string PrefabName { get; } internal string ItemName { get; } internal AcceptedInput(string prefabName, string itemName) { PrefabName = prefabName; ItemName = itemName; } } internal enum TransferResult { Transferred, NoItems, Full, NotReady, Failed } private const float IdleRetryDelay = 5f; private const float StaleRetryDelay = 2f; private static readonly Dictionary<object, float> _fuelChecks = new Dictionary<object, float>(); private static readonly Dictionary<object, float> _inputChecks = new Dictionary<object, float>(); private static float _nextCleanup; internal static void Clear() { _fuelChecks.Clear(); _inputChecks.Clear(); _nextCleanup = 0f; } private static bool HasLocalOwnership(ZNetView networkView) { return networkView.IsOwner(); } internal static void FireplaceInteracted(Fireplace fireplace, Humanoid user, bool interactionReady, bool vanillaHandled) { if (!(!interactionReady || vanillaHandled) && SupplyFromStoragePlugin.FuelAutomationEnabled && !((Object)(object)user != (Object)(object)Player.m_localPlayer)) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null && ReadyToCheck(_fuelChecks, fireplace)) { Defer(_fuelChecks, fireplace, CanUse(fireplace, val) ? TransferFireplaceFuel(fireplace, val, NearbyStorage.Find(val)) : TransferResult.NotReady); } } } internal static bool TryHandleCookingStationFuel(CookingStation station, Humanoid user, ItemData item) { Player val = (Player)(object)((user is Player) ? user : null); if (val == null || !CanUse(station, val) || !station.m_useFuel || station.GetFuel() > (float)(station.m_maxFuel - 1)) { return false; } string fuelName = station.m_fuelItem?.m_itemData?.m_shared?.m_name; return TryHandleFuelRequest(station, user, item, fuelName, (Player localPlayer) => TransferCookingStationFuel(station, localPlayer, NearbyStorage.Find(localPlayer))); } internal static bool TryHandleSmelterFuel(Smelter smelter, Humanoid user, ItemData item) { Player val = (Player)(object)((user is Player) ? user : null); if (val == null || !CanUse(smelter, val) || smelter.GetFuel() > (float)(smelter.m_maxFuel - 1)) { return false; } string fuelName = smelter.m_fuelItem?.m_itemData?.m_shared?.m_name; return TryHandleFuelRequest(smelter, user, item, fuelName, (Player localPlayer) => TransferSmelterFuel(smelter, localPlayer, NearbyStorage.Find(localPlayer))); } internal static bool TryHandleSmelterInput(Smelter smelter, Humanoid user, ItemData item, out bool handledResult) { handledResult = false; if (item == null && SupplyFromStoragePlugin.ProcessingAutomationEnabled && !((Object)(object)user != (Object)(object)Player.m_localPlayer)) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null && !HasProcessableInventoryItem(smelter, val)) { if (!CanUse(smelter, val)) { Defer(_inputChecks, smelter, TransferResult.NotReady); return false; } List<Container> sources = NearbyStorage.Find(val); bool flag = HasAccessibleSmelterInput(smelter, val, sources); if (!ReadyToCheck(_inputChecks, smelter)) { return flag; } if (!flag) { Defer(_inputChecks, smelter, TransferResult.NoItems); return false; } if (smelter.GetQueueSize() >= smelter.m_maxOre) { ((Character)user).Message((MessageType)2, "$msg_itsfull", 0, (Sprite)null, false); Defer(_inputChecks, smelter, TransferResult.Full); return true; } TransferResult transferResult = TransferSmelterInputs(smelter, val, sources); Defer(_inputChecks, smelter, transferResult); if (transferResult == TransferResult.Transferred) { handledResult = true; return true; } if (transferResult == TransferResult.Failed && flag) { return true; } return false; } } return false; } internal static TransferResult TransferFireplaceFuel(Fireplace fireplace, Player player, IReadOnlyList<Container> sources) { if (!CanUse(fireplace, player) || !fireplace.m_canRefill || fireplace.m_infiniteFuel || (Object)(object)fireplace.m_fuelItem == (Object)null) { return TransferResult.NotReady; } string fuelName = fireplace.m_fuelItem.m_itemData?.m_shared?.m_name; return TransferFuel(fireplace, player, sources, fuelName, fireplace.m_maxFuel, () => ReadFireplaceFuel(fireplace), delegate { fireplace.RPC_AddFuel(player.GetPlayerID()); }); } internal static TransferResult TransferCookingStationFuel(CookingStation station, Player player, IReadOnlyList<Container> sources) { if (!CanUse(station, player) || !station.m_useFuel || (Object)(object)station.m_fuelItem == (Object)null) { return TransferResult.NotReady; } string fuelName = station.m_fuelItem.m_itemData?.m_shared?.m_name; return TransferFuel((object)station, player, sources, fuelName, (float)station.m_maxFuel, (Func<float>)station.GetFuel, (Action)delegate { station.RPC_AddFuel(player.GetPlayerID()); }); } internal static TransferResult TransferSmelterFuel(Smelter smelter, Player player, IReadOnlyList<Container> sources) { if (!CanUse(smelter, player) || (Object)(object)smelter.m_fuelItem == (Object)null) { return TransferResult.NotReady; } string fuelName = smelter.m_fuelItem.m_itemData?.m_shared?.m_name; return TransferFuel((object)smelter, player, sources, fuelName, (float)smelter.m_maxFuel, (Func<float>)smelter.GetFuel, (Action)delegate { smelter.RPC_AddFuel(player.GetPlayerID()); }); } internal static TransferResult TransferSmelterInputs(Smelter smelter, Player player, IReadOnlyList<Container> sources) { if (!CanUse(smelter, player) || smelter.m_maxOre <= 0 || smelter.m_conversion == null) { return TransferResult.NotReady; } List<AcceptedInput> list = new List<AcceptedInput>(); foreach (ItemConversion item in smelter.m_conversion) { ItemDrop val = item?.m_from; if (!((Object)(object)val == (Object)null) && val.m_itemData?.m_shared != null && !string.IsNullOrEmpty(((Object)val).name) && smelter.IsItemAllowed(((Object)val).name)) { list.Add(new AcceptedInput(((Object)val).name, val.m_itemData.m_shared.m_name)); } } if (list.Count == 0) { return TransferResult.NoItems; } if (smelter.GetQueueSize() >= smelter.m_maxOre) { return TransferResult.Full; } foreach (Container source in sources) { if (!TryGetInventory(source, player, out var inventory)) { continue; } ItemData[] array = inventory.GetAllItems().ToArray(); foreach (ItemData val2 in array) { if (val2?.m_shared == null || val2.m_stack <= 0) { continue; } foreach (AcceptedInput accepted in list) { if (!(val2.m_shared.m_name != accepted.ItemName)) { int before = smelter.GetQueueSize(); return (!ConsumeOne(source, inventory, player, smelter, val2, () => smelter.IsItemAllowed(accepted.PrefabName) && smelter.GetQueueSize() < smelter.m_maxOre, delegate { smelter.RPC_AddOre(player.GetPlayerID(), accepted.PrefabName, false); }, () => smelter.GetQueueSize() == before + 1)) ? TransferResult.Failed : TransferResult.Transferred; } } } } return TransferResult.NoItems; } private static bool TryHandleFuelRequest(object station, Humanoid user, ItemData item, string fuelName, Func<Player, TransferResult> transfer) { if (item == null && SupplyFromStoragePlugin.FuelAutomationEnabled && !((Object)(object)user != (Object)(object)Player.m_localPlayer)) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null && !string.IsNullOrEmpty(fuelName) && ((Humanoid)val).GetInventory() != null && !((Humanoid)val).GetInventory().HaveItem(fuelName, true) && CanUse(station, val) && ReadyToCheck(_fuelChecks, station)) { TransferResult transferResult = transfer(val); Defer(_fuelChecks, station, transferResult); if (transferResult != TransferResult.Transferred) { return false; } ((Character)val).Message((MessageType)2, "$msg_added " + fuelName, 0, (Sprite)null, false); return true; } } return false; } private static TransferResult TransferFuel(object station, Player player, IReadOnlyList<Container> sources, string fuelName, float maximumFuel, Func<float> getFuel, Action addFuel) { if (string.IsNullOrEmpty(fuelName)) { return TransferResult.NotReady; } if (!HasFuelSpace(getFuel(), maximumFuel)) { return TransferResult.Full; } foreach (Container source in sources) { if (!TryGetInventory(source, player, out var inventory)) { continue; } ItemData[] array = inventory.GetAllItems().ToArray(); foreach (ItemData val in array) { if (val?.m_shared != null && !(val.m_shared.m_name != fuelName) && val.m_stack > 0) { float before = getFuel(); return (!ConsumeOne(source, inventory, player, station, val, () => HasFuelSpace(getFuel(), maximumFuel), addFuel, () => getFuel() > before)) ? TransferResult.Failed : TransferResult.Transferred; } } } return TransferResult.NoItems; } private static bool ConsumeOne(Container container, Inventory inventory, Player player, object station, ItemData item, Func<bool> canInsert, Action insert, Func<bool> confirmed) { if (!TryGetInventory(container, player, out var inventory2) || inventory2 != inventory || !inventory.ContainsItem(item) || item.m_stack <= 0 || !CanUse(station, player) || !canInsert()) { SupplyFromStoragePlugin.Log.LogWarning((object)"Storage or machine state changed before transfer; no input 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)) { SupplyFromStoragePlugin.Log.LogWarning((object)"Storage item changed before transfer; no machine input was added."); return false; } Exception ex = null; try { insert(); } catch (Exception ex2) { ex = ex2; } bool flag; try { flag = confirmed(); } catch (Exception arg) { SupplyFromStoragePlugin.Log.LogError((object)$"Could not confirm machine transfer; storage item remains consumed to avoid duplicate insertion. {arg}"); return false; } if (flag) { if (ex != null) { SupplyFromStoragePlugin.Log.LogError((object)$"Machine accepted the storage item, but its transfer callback threw after insertion. {ex}"); } return true; } bool flag2; try { flag2 = RestoreItem(inventory, item, stack, originalCount); } catch (Exception arg2) { SupplyFromStoragePlugin.Log.LogError((object)$"Machine rejected an item and storage restoration threw. {arg2}"); throw; } SupplyFromStoragePlugin.Log.LogError((object)("Machine rejected a storage transfer; source item 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; } private static float ReadFireplaceFuel(Fireplace fireplace) { return fireplace.m_nview.GetZDO().GetFloat(ZDOVars.s_fuel, 0f); } private 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; } private static bool HasProcessableInventoryItem(Smelter smelter, Player player) { if (smelter?.m_conversion == null || ((player != null) ? ((Humanoid)player).GetInventory() : null) == null) { return false; } List<ItemData> allItems = ((Humanoid)player).GetInventory().GetAllItems(); foreach (ItemConversion item in smelter.m_conversion) { string itemName = item?.m_from?.m_itemData?.m_shared?.m_name; if (!string.IsNullOrEmpty(itemName) && allItems.Any((ItemData item) => item?.m_shared?.m_name == itemName && item.m_stack > 0)) { return true; } } return false; } private static bool HasAccessibleSmelterInput(Smelter smelter, Player player, IReadOnlyList<Container> sources) { if (smelter?.m_conversion == null) { return false; } foreach (Container source in sources) { if (!TryGetInventory(source, player, out var inventory)) { continue; } foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem?.m_shared == null || allItem.m_stack <= 0) { continue; } foreach (ItemConversion item in smelter.m_conversion) { ItemDrop val = item?.m_from; if (val?.m_itemData?.m_shared != null && val.m_itemData.m_shared.m_name == allItem.m_shared.m_name && !string.IsNullOrEmpty(((Object)val).name) && smelter.IsItemAllowed(((Object)val).name)) { return true; } } } } return false; } private static bool CanUse(Smelter smelter, Player player) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)smelter != (Object)null && ((Behaviour)smelter).isActiveAndEnabled && (Object)(object)smelter.m_nview != (Object)null && smelter.m_nview.IsValid() && HasLocalOwnership(smelter.m_nview)) { return InRange(((Component)smelter).transform.position, player); } return false; } private static bool CanUse(CookingStation station, Player player) { //IL_003f: 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() && HasLocalOwnership(station.m_nview)) { return InRange(((Component)station).transform.position, player); } return false; } private static bool CanUse(Fireplace fireplace, Player player) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)fireplace != (Object)null && ((Behaviour)fireplace).isActiveAndEnabled && (Object)(object)fireplace.m_nview != (Object)null && fireplace.m_nview.IsValid() && HasLocalOwnership(fireplace.m_nview)) { return InRange(((Component)fireplace).transform.position, player); } return false; } private static bool CanUse(object station, Player player) { Smelter val = (Smelter)((station is Smelter) ? station : null); if (val == null) { CookingStation val2 = (CookingStation)((station is CookingStation) ? station : null); if (val2 == null) { Fireplace val3 = (Fireplace)((station is Fireplace) ? station : null); if (val3 != null) { return CanUse(val3, player); } return false; } return CanUse(val2, player); } return CanUse(val, player); } private static bool InRange(Vector3 position, Player player) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player != (Object)null) { Vector3 val = position - ((Component)player).transform.position; return ((Vector3)(ref val)).sqrMagnitude <= 400f; } return false; } private static bool HasFuelSpace(float fuel, float maximum) { return Math.Ceiling(fuel) < (double)maximum; } private static bool ReadyToCheck(Dictionary<object, float> checks, object station) { if (station == null) { return false; } float now = Time.realtimeSinceStartup; if (now >= _nextCleanup) { KeyValuePair<object, float>[] array = checks.Where((KeyValuePair<object, float> pair) => pair.Value <= now).ToArray(); foreach (KeyValuePair<object, float> keyValuePair in array) { checks.Remove(keyValuePair.Key); } _nextCleanup = now + 5f; } if (checks.TryGetValue(station, out var value)) { return now >= value; } return true; } private static void Defer(Dictionary<object, float> checks, object station, TransferResult result) { float num; switch (result) { case TransferResult.Transferred: checks.Remove(station); return; default: num = 5f; break; case TransferResult.Failed: num = 2f; break; } float num2 = num; checks[station] = Time.realtimeSinceStartup + num2; } } } 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) { } } }