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 Crafting Storage Link v1.7.1
BepInEx/plugins/CraftingStorageLink/CraftingStorageLink.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("CraftingStorageLink")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.7.1.0")] [assembly: AssemblyInformationalVersion("1.7.1")] [assembly: AssemblyProduct("CraftingStorageLink")] [assembly: AssemblyTitle("CraftingStorageLink")] [assembly: AssemblyVersion("1.7.1.0")] [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 CraftingStorageLink { [BepInPlugin("com.custom.valheim.craftingstoragelink", "CraftingStorageLink", "1.7.1")] public sealed class CraftingStorageLinkPlugin : BaseUnityPlugin { private sealed class CraftContext { public readonly Player Player; public readonly Inventory PlayerInventory; public readonly CraftingStation Station; public readonly Recipe Recipe; public CraftContext(Player player, CraftingStation station, Recipe recipe) { Player = player; PlayerInventory = ((Humanoid)player).GetInventory(); Station = station; Recipe = recipe; } } private sealed class BuildContext { public readonly Player Player; public readonly Inventory PlayerInventory; public readonly CraftingStation Station; public readonly Piece Piece; public readonly Vector3 Origin; public BuildContext(Player player, CraftingStation station, Piece piece) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) Player = player; PlayerInventory = ((Humanoid)player).GetInventory(); Station = station; Piece = piece; Origin = (((Object)(object)station != (Object)null) ? ((Component)station).transform.position : ((Component)player).transform.position); } } private sealed class PullItem { public readonly string Name; public readonly int Quality; public int Required; public int BeforeTotal; public PullItem(string name, int quality, int required) { Name = name; Quality = quality; Required = required; } } private sealed class PullPlan { public readonly Player Player; public readonly CraftingStation Station; public readonly Vector3 Origin; public readonly List<PullItem> Items = new List<PullItem>(); public PullPlan(Player player, CraftingStation station, Vector3 origin) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) Player = player; Station = station; Origin = origin; } public void AddRequired(string name, int quality, int amount) { if (amount <= 0) { return; } for (int i = 0; i < Items.Count; i++) { PullItem pullItem = Items[i]; if (pullItem.Name == name && pullItem.Quality == quality) { pullItem.Required += amount; return; } } Items.Add(new PullItem(name, quality, amount)); } } private sealed class ContainerCandidate { public readonly Container Container; public readonly float DistanceSqr; public ContainerCandidate(Container container, float distanceSqr) { Container = container; DistanceSqr = distanceSqr; } } private sealed class ProcessorFeedTransaction { public readonly Player Player; public readonly Smelter Processor; public readonly Container SourceContainer; public readonly ItemData RestoreTemplate; public readonly string ItemName; public readonly int Quality; public readonly int PlayerCountBefore; public readonly string InteractionName; public ProcessorFeedTransaction(Player player, Smelter processor, Container sourceContainer, ItemData restoreTemplate, string itemName, int quality, int playerCountBefore, string interactionName) { Player = player; Processor = processor; SourceContainer = sourceContainer; RestoreTemplate = restoreTemplate; ItemName = itemName; Quality = quality; PlayerCountBefore = playerCountBefore; InteractionName = interactionName; } } private sealed class LinkedCountFrameEntry { public readonly int Count; public readonly bool Complete; public LinkedCountFrameEntry(int count, bool complete) { Count = count; Complete = complete; } } private enum LinkContextKind { Disabled, RecipeRequirement, RecipeDisplay, Crafting, BuildRequirement, BuildHudDisplay, Placement } private sealed class LinkContext { public readonly LinkContextKind Kind; public readonly Player Player; public readonly Inventory PlayerInventory; public readonly CraftingStation Station; public readonly Recipe Recipe; public readonly Piece Piece; public readonly Vector3 Origin; public readonly bool HasOrigin; public readonly bool Enabled; private LinkContext(LinkContextKind kind) { Kind = kind; Enabled = false; } public LinkContext(LinkContextKind kind, Player player, CraftingStation station, Recipe recipe) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Kind = kind; Player = player; PlayerInventory = (((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null); Station = station; Recipe = recipe; Piece = null; Origin = (((Object)(object)station != (Object)null) ? ((Component)station).transform.position : Vector3.zero); HasOrigin = (Object)(object)station != (Object)null; Enabled = (Object)(object)player != (Object)null && PlayerInventory != null && HasOrigin; } public LinkContext(LinkContextKind kind, Player player, CraftingStation station, Piece piece) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) Kind = kind; Player = player; PlayerInventory = (((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null); Station = station; Recipe = null; Piece = piece; Origin = (((Object)(object)station != (Object)null) ? ((Component)station).transform.position : (((Object)(object)player != (Object)null) ? ((Component)player).transform.position : Vector3.zero)); HasOrigin = (Object)(object)player != (Object)null; Enabled = (Object)(object)player != (Object)null && PlayerInventory != null && (Object)(object)piece != (Object)null && HasOrigin; } public static LinkContext Disabled(LinkContextKind kind) { return new LinkContext(kind); } } private sealed class ContextGuard { private readonly LinkContext _context; private bool _disposed; public ContextGuard(LinkContext context) { _context = context; } public void Dispose() { if (_disposed) { return; } _disposed = true; Stack<LinkContext> contextStack = GetContextStack(); if (contextStack.Count == 0) { return; } if (contextStack.Peek() == _context) { contextStack.Pop(); return; } Stack<LinkContext> stack = new Stack<LinkContext>(); while (contextStack.Count > 0 && contextStack.Peek() != _context) { stack.Push(contextStack.Pop()); } if (contextStack.Count > 0) { contextStack.Pop(); } while (stack.Count > 0) { contextStack.Push(stack.Pop()); } } } [HarmonyPatch] private static class ContainerLifecycleRegistryPatch { private static IEnumerable<MethodBase> TargetMethods() { MethodInfo[] methods = typeof(Container).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int i = 0; while (i < methods.Length) { MethodInfo methodInfo = methods[i]; if (methodInfo.Name == "Awake" || methodInfo.Name == "OnDestroyed" || methodInfo.Name == "OnDestroy") { yield return methodInfo; } int num = i + 1; i = num; } } private static void Prefix(Container __instance, MethodBase __originalMethod) { if (!(__originalMethod == null) && !(__originalMethod.Name == "Awake")) { UnregisterContainer(__instance); } } private static void Postfix(Container __instance, MethodBase __originalMethod) { if (__originalMethod != null && __originalMethod.Name == "Awake") { RegisterContainer(__instance); } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class ZNetOnNewConnectionPatch { private static void Postfix(ZNet __instance, ZNetPeer peer) { RegisterConfigRpc(__instance, peer); } } [HarmonyPatch] private static class PlayerHaveRequirementItemsPatch { private static IEnumerable<MethodBase> TargetMethods() { MethodInfo[] methods = typeof(Player).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int i = 0; while (i < methods.Length) { MethodInfo methodInfo = methods[i]; if (methodInfo.Name == "HaveRequirementItems" && methodInfo.ReturnType == typeof(bool)) { yield return methodInfo; } int num = i + 1; i = num; } } private static void Prefix(Player __instance, MethodBase __originalMethod, object[] __args, ref ContextGuard __state) { try { Recipe val = null; bool discover = false; bool flag = false; ParameterInfo[] array = ((__originalMethod != null) ? __originalMethod.GetParameters() : null); if (__args != null) { for (int i = 0; i < __args.Length; i++) { object obj = __args[i]; ParameterInfo parameterInfo = ((array != null && i < array.Length) ? array[i] : null); if ((Object)(object)val == (Object)null) { Recipe val2 = (Recipe)((obj is Recipe) ? obj : null); if (val2 != null) { val = val2; } } if (obj is bool flag2) { string a = parameterInfo?.Name; if (!flag || string.Equals(a, "discover", StringComparison.OrdinalIgnoreCase)) { discover = flag2; flag = true; string.Equals(a, "discover", StringComparison.OrdinalIgnoreCase); } } } } __state = BeginRequirementCheck(__instance, val, discover); } catch (Exception ex) { LogRuntimeFault("prefix-have-requirement-items", "CraftingStorageLink recovered while starting HaveRequirementItems context: " + ex.GetType().Name + ": " + ex.Message); __state = PushContext(null, LinkContextKind.RecipeRequirement); } } private static void Postfix(ContextGuard __state) { __state?.Dispose(); } private static Exception Finalizer(Exception __exception, ContextGuard __state) { __state?.Dispose(); return __exception; } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Recipe), typeof(bool), typeof(int), typeof(int) })] private static class PlayerHaveRecipeRequirementsPatch { private static void Prefix(Player __instance, Recipe recipe, bool discover, ref ContextGuard __state) { try { __state = BeginRequirementCheck(__instance, recipe, discover); } catch (Exception ex) { LogRuntimeFault("prefix-recipe-requirements", "CraftingStorageLink recovered while starting a recipe requirement context: " + ex.GetType().Name + ": " + ex.Message); __state = PushContext(null, LinkContextKind.RecipeRequirement); } } private static void Postfix(ContextGuard __state) { __state?.Dispose(); } private static Exception Finalizer(Exception __exception, ContextGuard __state) { __state?.Dispose(); return __exception; } } [HarmonyPatch(typeof(InventoryGui), "SetupRequirement", new Type[] { typeof(Transform), typeof(Requirement), typeof(Player), typeof(bool), typeof(int), typeof(int) })] private static class InventoryGuiSetupRequirementPatch { private static void Prefix(InventoryGui __instance, object[] __args, ref ContextGuard __state) { try { Player player = (Player)((__args != null && __args.Length > 2) ? /*isinst with value type is only supported in some contexts*/: null); __state = BeginRequirementDisplay(__instance, player); } catch (Exception ex) { LogRuntimeFault("prefix-recipe-display", "CraftingStorageLink recovered while starting a recipe display context: " + ex.GetType().Name + ": " + ex.Message); __state = PushContext(null, LinkContextKind.RecipeDisplay); } } [HarmonyPriority(0)] private static void Postfix(InventoryGui __instance, object[] __args, ContextGuard __state) { __state?.Dispose(); if (!ShowAvailableResourceCounts || __args == null || __args.Length < 6) { return; } try { object obj = __args[0]; Transform row = (Transform)((obj is Transform) ? obj : null); object obj2 = __args[1]; Requirement requirement = (Requirement)((obj2 is Requirement) ? obj2 : null); object obj3 = __args[2]; Player player = (Player)((obj3 is Player) ? obj3 : null); int qualityLevel = ((__args[4] is int num) ? num : 0); int craftMultiplier = ((!(__args[5] is int num2)) ? 1 : num2); StabilizeCraftRequirementDisplay(__instance, row, requirement, player, qualityLevel, craftMultiplier); } catch (Exception ex) { DebugLog("Could not update crafting available-resource count: " + ex.GetType().Name + ": " + ex.Message); } } private static Exception Finalizer(Exception __exception, ContextGuard __state) { __state?.Dispose(); return __exception; } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting", new Type[] { typeof(Player) })] private static class InventoryGuiDoCraftingPatch { private static void Prefix(InventoryGui __instance, Player player, ref ContextGuard __state) { try { __state = BeginCrafting(__instance, player); } catch (Exception ex) { LogRuntimeFault("prefix-crafting", "CraftingStorageLink recovered while starting a crafting context: " + ex.GetType().Name + ": " + ex.Message); __state = PushContext(null, LinkContextKind.Crafting); } } private static void Postfix(ContextGuard __state) { __state?.Dispose(); } private static Exception Finalizer(Exception __exception, ContextGuard __state) { __state?.Dispose(); return __exception; } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Piece), typeof(RequirementMode) })] private static class PlayerHavePieceRequirementsPatch { private static void Prefix(Player __instance, Piece piece, RequirementMode mode, ref ContextGuard __state) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) try { __state = BeginBuildRequirementCheck(__instance, piece, mode); } catch (Exception ex) { LogRuntimeFault("prefix-build-requirements", "CraftingStorageLink recovered while starting a build requirement context: " + ex.GetType().Name + ": " + ex.Message); __state = PushContext(null, LinkContextKind.BuildRequirement); } } private static void Postfix(ContextGuard __state) { __state?.Dispose(); } private static Exception Finalizer(Exception __exception, ContextGuard __state) { __state?.Dispose(); return __exception; } } [HarmonyPatch(typeof(Player), "UpdatePlacement", new Type[] { typeof(bool), typeof(float) })] private static class PlayerUpdatePlacementPatch { private static void Prefix(Player __instance, ref ContextGuard __state) { try { __state = BeginPlacement(__instance); } catch (Exception ex) { LogRuntimeFault("prefix-placement", "CraftingStorageLink recovered while starting a placement context: " + ex.GetType().Name + ": " + ex.Message); __state = PushContext(null, LinkContextKind.Placement); } } private static void Postfix(ContextGuard __state) { __state?.Dispose(); } private static Exception Finalizer(Exception __exception, ContextGuard __state) { __state?.Dispose(); return __exception; } } [HarmonyPatch] private static class HudSetupPieceInfoPatch { private static IEnumerable<MethodBase> TargetMethods() { MethodInfo[] methods = typeof(Hud).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int i = 0; while (i < methods.Length) { if (methods[i].Name == "SetupPieceInfo") { yield return methods[i]; } int num = i + 1; i = num; } } private static void Prefix(object[] __args, ref ContextGuard __state) { try { __state = BeginBuildHudDisplay(Player.m_localPlayer, __args); } catch (Exception ex) { LogRuntimeFault("prefix-build-hud", "CraftingStorageLink recovered while starting a Hammer HUD context: " + ex.GetType().Name + ": " + ex.Message); __state = PushContext(null, LinkContextKind.BuildHudDisplay); } } private static void Postfix(Hud __instance, object[] __args, ContextGuard __state) { __state?.Dispose(); try { Player localPlayer = Player.m_localPlayer; Piece piece = ResolveBuildHudPiece(localPlayer, __args); StabilizeBuildRequirementDisplay(__instance, localPlayer, piece); } catch (Exception ex) { DebugLog("Could not stabilize Hammer requirement display: " + ex.GetType().Name + ": " + ex.Message); } } private static Exception Finalizer(Exception __exception, ContextGuard __state) { __state?.Dispose(); return __exception; } } [HarmonyPatch] private static class SmelterLinkedFeedingPatch { private static IEnumerable<MethodBase> TargetMethods() { MethodInfo[] methods = typeof(Smelter).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int i = 0; while (i < methods.Length) { MethodInfo methodInfo = methods[i]; if (methodInfo.Name == "OnAddFuel" || methodInfo.Name == "OnAddFuelSwitch" || methodInfo.Name == "OnAddOre") { yield return methodInfo; } int num = i + 1; i = num; } } [HarmonyPriority(800)] private static void Prefix(Smelter __instance, MethodBase __originalMethod, object[] __args, ref ProcessorFeedTransaction __state) { try { __state = BeginProcessorFeed(__instance, __originalMethod, __args); } catch (Exception ex) { LogRuntimeFault("processor-feed-prefix-" + ((__originalMethod != null) ? __originalMethod.Name : "unknown"), "CraftingStorageLink recovered while staging a linked processor item: " + ex.GetType().Name + ": " + ex.Message); __state = null; } } [HarmonyPriority(0)] private static void Postfix(ProcessorFeedTransaction __state) { try { CompleteProcessorFeed(__state); } catch (Exception ex) { LogRuntimeFault("processor-feed-postfix", "CraftingStorageLink recovered while finalizing a linked processor item: " + ex.GetType().Name + ": " + ex.Message); } } private static Exception Finalizer(Exception __exception, ProcessorFeedTransaction __state) { if (__exception != null) { try { CompleteProcessorFeed(__state); } catch { } } return __exception; } } [HarmonyPatch(typeof(Inventory), "HaveItem", new Type[] { typeof(string), typeof(bool) })] private static class InventoryHaveItemPatch { private static void Postfix(Inventory __instance, string name, bool matchWorldLevel, ref bool __result) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) try { if (!__result && __instance != null) { LinkContext currentContext = CurrentContext; if (currentContext != null && currentContext.Enabled && __instance == currentContext.PlayerInventory && (currentContext.Kind == LinkContextKind.BuildRequirement || currentContext.Kind == LinkContextKind.Placement)) { __result = CountLinkedItem(currentContext.Player, currentContext.Station, currentContext.Origin, name, -1, matchWorldLevel, 1) > 0; } } } catch (Exception ex) { LogRuntimeFault("linked-haveitem-postfix", "CraftingStorageLink recovered from a HaveItem linked-storage failure: " + ex.GetType().Name + ": " + ex.Message); ClearContainerCache(); } } } [HarmonyPatch(typeof(Inventory), "CountItems", new Type[] { typeof(string), typeof(int), typeof(bool) })] private static class InventoryCountItemsPatch { private static void Postfix(Inventory __instance, string name, int quality, bool matchWorldLevel, ref int __result) { try { AddLinkedCount(__instance, name, quality, matchWorldLevel, ref __result); } catch (Exception ex) { LogRuntimeFault("inventory-countitems-postfix", "CraftingStorageLink recovered from Inventory.CountItems postfix: " + ex.GetType().Name + ": " + ex.Message); ClearContainerCache(); } } } [HarmonyPatch(typeof(Player), "ConsumeResources", new Type[] { typeof(Requirement[]), typeof(int), typeof(int), typeof(int) })] private static class PlayerConsumeResourcesPatch { private static void Prefix(Player __instance, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier, ref PullPlan __state) { __state = CreatePullPlan(__instance, requirements, qualityLevel, itemQuality, multiplier); } [HarmonyPriority(0)] private static void Postfix(PullPlan __state) { PayPullPlan(__state); } } public const string PluginGuid = "com.custom.valheim.craftingstoragelink"; public const string PluginName = "CraftingStorageLink"; public const string PluginVersion = "1.7.1"; private static ConfigEntry<bool> _enabled; private static ConfigEntry<bool> _craftingEnabled; private static ConfigEntry<bool> _hammerBuildingEnabled; private static ConfigEntry<bool> _processorFeedingEnabled; private static ConfigEntry<float> _radius; private static ConfigEntry<bool> _playerBuiltOnly; private static ConfigEntry<bool> _allowShipContainers; private static ConfigEntry<bool> _allowCartContainers; private static ConfigEntry<bool> _respectContainerAccess; private static ConfigEntry<bool> _respectWards; private static ConfigEntry<bool> _skipInUseContainers; private static ConfigEntry<bool> _claimOwnershipWhenPulling; private static ConfigEntry<bool> _respectMultiUserChestIgnore; private static ConfigEntry<bool> _showAvailableResourceCounts; private static ConfigEntry<KeyboardShortcut> _settingsMenuShortcut; private static ConfigEntry<bool> _clientMasterPreference; private static ConfigEntry<bool> _clientCraftingPreference; private static ConfigEntry<bool> _clientHammerPreference; private static ConfigEntry<bool> _clientProcessorPreference; private static ConfigEntry<bool> _debugLogging; private const string ConfigRpcName = "CSL_Config_v1"; private const int ConfigProtocolVersion = 3; private static bool _serverConfigReceived; private static bool _serverEnabled; private static bool _serverCraftingEnabled; private static bool _serverHammerBuildingEnabled; private static bool _serverProcessorFeedingEnabled; private static float _serverRadius; private static bool _serverPlayerBuiltOnly; private static bool _serverAllowShipContainers; private static bool _serverAllowCartContainers; private static bool _serverRespectContainerAccess; private static bool _serverRespectWards; private static bool _serverSkipInUseContainers; private static bool _serverClaimOwnershipWhenPulling; private static bool _serverRespectMultiUserChestIgnore; private static ManualLogSource _log; private static FieldInfo _inventoryGuiCraftRecipeField; private static FieldInfo _inventoryGuiSelectedRecipeField; private static FieldInfo _inventoryGuiCurrentContainerField; private static FieldInfo _containerNViewField; private static MethodInfo _containerLoadMethod; private static MethodInfo _containerSaveMethod; private static MethodInfo _containerCheckAccessMethod; private static MethodInfo _inventoryAddItemDataMethod; private static MethodInfo _inventoryRemoveOneItemMethod; private static MethodInfo _itemDataCloneMethod; private static FieldInfo _smelterFuelItemField; private static FieldInfo _smelterFuelItemsField; private static FieldInfo _smelterConversionField; private static FieldInfo _smelterConversionsField; private static MethodInfo _znetGetPeersMethod; private bool _settingsWindowOpen; private Rect _settingsWindowRect = new Rect(48f, 48f, 500f, 475f); private Vector2 _settingsScrollPosition = Vector2.zero; private bool _savedCursorVisible; private CursorLockMode _savedCursorLockMode; [ThreadStatic] private static Stack<LinkContext> _contextStack; private static Player _cachedPlayer; private static CraftingStation _cachedStation; private static Vector3 _cachedOrigin; private static bool _cachedOriginSet; private static float _cachedContainersAtRealtime = -1000f; private static List<ContainerCandidate> _cachedContainers; private static readonly HashSet<Container> _containerRegistry = new HashSet<Container>(); private static bool _containerRegistryBootstrapped; private const float ContainerDiscoveryCacheSeconds = 0.25f; private const float ContainerDiscoveryOriginToleranceSqr = 1f; private static int _linkedCountCacheFrame = -1; private static readonly Dictionary<string, LinkedCountFrameEntry> _linkedCountFrameCache = new Dictionary<string, LinkedCountFrameEntry>(); private Harmony _harmony; private int _lastLocalPlayerInstanceId = int.MinValue; private static readonly Dictionary<string, float> _runtimeFaultLogTimes = new Dictionary<string, float>(); private const float RuntimeFaultLogIntervalSeconds = 10f; private static bool UseSyncedServerConfig { get { if (_serverConfigReceived && (Object)(object)ZNet.instance != (Object)null) { return !ZNet.instance.IsServer(); } return false; } } private static bool ApplyClientPreferences { get { if (!((Object)(object)ZNet.instance == (Object)null)) { return !ZNet.instance.IsServer(); } return true; } } private static bool EffectiveEnabled { get { if (UseSyncedServerConfig ? _serverEnabled : _enabled.Value) { return ClientPreference(_clientMasterPreference); } return false; } } private static bool EffectiveCraftingEnabled { get { if (UseSyncedServerConfig ? _serverCraftingEnabled : _craftingEnabled.Value) { return ClientPreference(_clientCraftingPreference); } return false; } } private static bool EffectiveHammerBuildingEnabled { get { if (UseSyncedServerConfig ? _serverHammerBuildingEnabled : _hammerBuildingEnabled.Value) { return ClientPreference(_clientHammerPreference); } return false; } } private static bool EffectiveProcessorFeedingEnabled { get { if (UseSyncedServerConfig ? _serverProcessorFeedingEnabled : _processorFeedingEnabled.Value) { return ClientPreference(_clientProcessorPreference); } return false; } } private static float EffectiveRadius { get { if (!UseSyncedServerConfig) { return _radius.Value; } return _serverRadius; } } private static bool EffectivePlayerBuiltOnly { get { if (!UseSyncedServerConfig) { return _playerBuiltOnly.Value; } return _serverPlayerBuiltOnly; } } private static bool EffectiveAllowShipContainers { get { if (!UseSyncedServerConfig) { return _allowShipContainers.Value; } return _serverAllowShipContainers; } } private static bool EffectiveAllowCartContainers { get { if (!UseSyncedServerConfig) { return _allowCartContainers.Value; } return _serverAllowCartContainers; } } private static bool EffectiveRespectContainerAccess { get { if (!UseSyncedServerConfig) { return _respectContainerAccess.Value; } return _serverRespectContainerAccess; } } private static bool EffectiveRespectWards { get { if (!UseSyncedServerConfig) { return _respectWards.Value; } return _serverRespectWards; } } private static bool EffectiveSkipInUseContainers { get { if (!UseSyncedServerConfig) { return _skipInUseContainers.Value; } return _serverSkipInUseContainers; } } private static bool EffectiveClaimOwnershipWhenPulling { get { if (!UseSyncedServerConfig) { return _claimOwnershipWhenPulling.Value; } return _serverClaimOwnershipWhenPulling; } } private static bool EffectiveRespectMultiUserChestIgnore { get { if (!UseSyncedServerConfig) { return _respectMultiUserChestIgnore.Value; } return _serverRespectMultiUserChestIgnore; } } private static LinkContext CurrentContext { get { Stack<LinkContext> contextStack = GetContextStack(); if (contextStack.Count <= 0) { return null; } return contextStack.Peek(); } } private static bool ShowAvailableResourceCounts { get { if (_showAvailableResourceCounts != null) { return _showAvailableResourceCounts.Value; } return false; } } private static bool ClientPreference(ConfigEntry<bool> entry) { if (ApplyClientPreferences && entry != null) { return entry.Value; } return true; } private static Stack<LinkContext> GetContextStack() { if (_contextStack == null) { _contextStack = new Stack<LinkContext>(); } return _contextStack; } private static ContextGuard PushContext(LinkContext context, LinkContextKind kind) { LinkContext linkContext = context ?? LinkContext.Disabled(kind); GetContextStack().Push(linkContext); return new ContextGuard(linkContext); } private void Awake() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Expected O, but got Unknown //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Expected O, but got Unknown //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Expected O, but got Unknown //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Expected O, but got Unknown //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Expected O, but got Unknown //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Expected O, but got Unknown //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_04e2: Unknown result type (might be due to invalid IL or missing references) //IL_04ec: Expected O, but got Unknown //IL_056e: Unknown result type (might be due to invalid IL or missing references) _log = ((BaseUnityPlugin)this).Logger; _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "Enabled", true, new ConfigDescription("Master switch for CraftingStorageLink. When false, recipe crafting, Hammer/build-tool linking, and processor linked feeding are disabled.", (AcceptableValueBase)null, Array.Empty<object>())); _craftingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "CraftingEnabled", true, new ConfigDescription("Allow crafting and upgrading in crafting-station UIs to count and consume resources from linked storage. Set false while leaving HammerBuildingEnabled=true for Hammer-only mode.", (AcceptableValueBase)null, Array.Empty<object>())); _hammerBuildingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "HammerBuildingEnabled", true, new ConfigDescription("Allow Hammer/build-tool placement costs to count and consume resources from nearby storage. Station-required pieces link around that station; stationless pieces such as Workbenches and Campfires link around the player.", (AcceptableValueBase)null, Array.Empty<object>())); _processorFeedingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - General", "ProcessorFeedingEnabled", true, new ConfigDescription("Allow normal player interactions with Smelter-based processors (smelters, blast furnaces, kilns, windmills, spinning wheels, eitr refineries, and compatible modded derivatives) to source one valid fuel/input item from linked storage when the player is not carrying a valid item. Vanilla processor capacity, queueing and RPC behavior remain in control.", (AcceptableValueBase)null, Array.Empty<object>())); _radius = ((BaseUnityPlugin)this).Config.Bind<float>("02 - Linking", "Radius", 20f, new ConfigDescription("Radius in metres used to find storage containers. Crafting uses the active station; stationless Hammer pieces use the player position; processor feeding uses the processor position.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>())); _playerBuiltOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("02 - Linking", "PlayerBuiltContainersOnly", true, new ConfigDescription("Ignore world-generated loot containers and only use containers attached to player-built pieces.", (AcceptableValueBase)null, Array.Empty<object>())); _allowShipContainers = ((BaseUnityPlugin)this).Config.Bind<bool>("02 - Linking", "AllowShipContainers", false, new ConfigDescription("Allow storage on ships to be linked. Disabled by default because moving networked containers are higher risk.", (AcceptableValueBase)null, Array.Empty<object>())); _allowCartContainers = ((BaseUnityPlugin)this).Config.Bind<bool>("02 - Linking", "AllowCartContainers", false, new ConfigDescription("Allow cart storage to be linked. Disabled by default because moving networked containers are higher risk.", (AcceptableValueBase)null, Array.Empty<object>())); _respectContainerAccess = ((BaseUnityPlugin)this).Config.Bind<bool>("03 - Safety", "RespectContainerAccess", true, new ConfigDescription("Do not use private/personal containers that the crafting player cannot access.", (AcceptableValueBase)null, Array.Empty<object>())); _respectWards = ((BaseUnityPlugin)this).Config.Bind<bool>("03 - Safety", "RespectWards", true, new ConfigDescription("Do not use containers blocked by ward/private-area access.", (AcceptableValueBase)null, Array.Empty<object>())); _skipInUseContainers = ((BaseUnityPlugin)this).Config.Bind<bool>("03 - Safety", "SkipInUseContainers", true, new ConfigDescription("Ignore containers that are in use by another player. The local player's currently open container remains eligible. Set this false only when your shared-container setup is transaction-safe.", (AcceptableValueBase)null, Array.Empty<object>())); _claimOwnershipWhenPulling = ((BaseUnityPlugin)this).Config.Bind<bool>("03 - Safety", "ClaimOwnershipWhenPulling", true, new ConfigDescription("If required, claim ZNetView ownership immediately before consuming from a linked container. Disable only for troubleshooting; non-owned containers will then be ignored.", (AcceptableValueBase)null, Array.Empty<object>())); _respectMultiUserChestIgnore = ((BaseUnityPlugin)this).Config.Bind<bool>("03 - Safety", "RespectMultiUserChestIgnore", true, new ConfigDescription("Honor MultiUserChest's MUC_Ignore flag when present on a container.", (AcceptableValueBase)null, Array.Empty<object>())); _showAvailableResourceCounts = ((BaseUnityPlugin)this).Config.Bind<bool>("04 - UI", "ShowAvailableResourceCounts", true, new ConfigDescription("Show total available resources (player inventory + linked storage) as Available/Required in crafting and Hammer requirement rows. This is a local display preference and is not server-synced.", (AcceptableValueBase)null, Array.Empty<object>())); _settingsMenuShortcut = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("04 - UI", "SettingsMenuShortcut", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Open/close the built-in CraftingStorageLink settings window. The default is F8."); _clientMasterPreference = ((BaseUnityPlugin)this).Config.Bind<bool>("05 - Client Preferences", "UseCraftingStorageLink", true, "Local opt-out for this client. On a dedicated server this cannot override a server-side disabled setting."); _clientCraftingPreference = ((BaseUnityPlugin)this).Config.Bind<bool>("05 - Client Preferences", "UseLinkedCrafting", true, "Local preference for linked crafting/upgrading when the server allows it."); _clientHammerPreference = ((BaseUnityPlugin)this).Config.Bind<bool>("05 - Client Preferences", "UseLinkedBuilding", true, "Local preference for linked Hammer/build-tool costs when the server allows it."); _clientProcessorPreference = ((BaseUnityPlugin)this).Config.Bind<bool>("05 - Client Preferences", "UseLinkedProcessorFeeding", true, "Local preference for linked smelter/refinery/processor feeding when the server allows it."); _debugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("99 - Debug", "DebugLogging", false, "Log linked container discovery and resource pulls on this machine."); _inventoryGuiCraftRecipeField = AccessTools.Field(typeof(InventoryGui), "m_craftRecipe"); _inventoryGuiSelectedRecipeField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe"); _inventoryGuiCurrentContainerField = AccessTools.Field(typeof(InventoryGui), "m_currentContainer"); _containerNViewField = AccessTools.Field(typeof(Container), "m_nview"); _containerLoadMethod = AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null); _containerSaveMethod = AccessTools.Method(typeof(Container), "Save", (Type[])null, (Type[])null); _containerCheckAccessMethod = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null); _inventoryAddItemDataMethod = ResolveSingleItemDataMethod(typeof(Inventory), "AddItem"); _inventoryRemoveOneItemMethod = ResolveSingleItemDataMethod(typeof(Inventory), "RemoveOneItem"); _itemDataCloneMethod = AccessTools.Method(typeof(ItemData), "Clone", Type.EmptyTypes, (Type[])null); _smelterFuelItemField = FindFieldNoWarning(typeof(Smelter), "m_fuelItem"); _smelterFuelItemsField = FindFieldNoWarning(typeof(Smelter), "m_fuelItems"); _smelterConversionField = FindFieldNoWarning(typeof(Smelter), "m_conversion"); _smelterConversionsField = FindFieldNoWarning(typeof(Smelter), "m_conversions"); _znetGetPeersMethod = AccessTools.Method(typeof(ZNet), "GetPeers", (Type[])null, (Type[])null); _harmony = new Harmony("com.custom.valheim.craftingstoragelink"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded. BepInEx-only build; crafting linking {2}; hammer linking {3}; processor feeding {4}; configured radius {5:0.#}m; settings GUI {6}.", "CraftingStorageLink", "1.7.1", EffectiveCraftingEnabled ? "on" : "off", EffectiveHammerBuildingEnabled ? "on" : "off", EffectiveProcessorFeedingEnabled ? "on" : "off", EffectiveRadius, _settingsMenuShortcut.Value)); if (_inventoryAddItemDataMethod == null || _itemDataCloneMethod == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Processor linked-feeding compatibility is incomplete: Inventory.AddItem(ItemData) or ItemData.Clone() could not be resolved. Crafting/Hammer linking still works normally; processor staging will safely no-op."); } } private void Update() { //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 (Application.isBatchMode || _settingsMenuShortcut == null) { return; } try { KeyboardShortcut value = _settingsMenuShortcut.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { SetSettingsWindowOpen(!_settingsWindowOpen); } } catch (Exception ex) { LogRuntimeFault("settings-gui-hotkey", "CraftingStorageLink settings hotkey failed safely: " + ex.GetType().Name + ": " + ex.Message); } } private void OnGUI() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (Application.isBatchMode || !_settingsWindowOpen) { return; } try { _settingsWindowRect = GUI.Window(1129532439, _settingsWindowRect, new WindowFunction(DrawSettingsWindow), "CraftingStorageLink 1.7.1"); } catch (Exception ex) { SetSettingsWindowOpen(open: false); LogRuntimeFault("settings-gui-draw", "CraftingStorageLink settings GUI closed after a draw error: " + ex.GetType().Name + ": " + ex.Message); } } private void DrawSettingsWindow(int windowId) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_03c0: Unknown result type (might be due to invalid IL or missing references) GUIStyle val = new GUIStyle(GUI.skin.label) { wordWrap = true }; GUIStyle val2 = new GUIStyle(GUI.skin.box); bool flag = (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(); bool flag2 = (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && _serverConfigReceived; _settingsScrollPosition = GUILayout.BeginScrollView(_settingsScrollPosition, false, true, Array.Empty<GUILayoutOption>()); GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); GUILayout.Label(flag ? "Host / single-player controls" : "Client preferences", val2, Array.Empty<GUILayoutOption>()); if (flag2) { GUILayout.Label("The server remains authoritative. These switches only let this client opt out of features the server allows; they cannot enable a feature the server has disabled.", val, Array.Empty<GUILayoutOption>()); } else if (flag) { GUILayout.Label("Changes below are applied immediately, saved to the local config, and pushed to connected modded clients.", val, Array.Empty<GUILayoutOption>()); } else { GUILayout.Label("Changes below are saved immediately. Server policy will be applied automatically after joining a dedicated server.", val, Array.Empty<GUILayoutOption>()); } GUILayout.Space(6f); if (flag) { DrawServerGameplayToggle("Enable CraftingStorageLink", _enabled); DrawServerGameplayToggle("Linked crafting & upgrading", _craftingEnabled); DrawServerGameplayToggle("Linked Hammer / build costs", _hammerBuildingEnabled); DrawServerGameplayToggle("Linked smelter / processor feeding", _processorFeedingEnabled); GUILayout.Space(6f); GUILayout.Label($"Linked-storage radius: {_radius.Value:0.#} m", val2, Array.Empty<GUILayoutOption>()); float num = GUILayout.HorizontalSlider(_radius.Value, 1f, 100f, Array.Empty<GUILayoutOption>()); num = Mathf.Round(num); if (Math.Abs(num - _radius.Value) >= 0.5f) { _radius.Value = num; SaveGuiConfigAndRefresh(broadcastServerConfig: true); } } else { DrawClientPreferenceToggle("Use CraftingStorageLink on this client", _clientMasterPreference, flag2 ? new bool?(_serverEnabled) : ((bool?)null)); DrawClientPreferenceToggle("Use linked crafting & upgrading", _clientCraftingPreference, flag2 ? new bool?(_serverEnabled && _serverCraftingEnabled) : ((bool?)null)); DrawClientPreferenceToggle("Use linked Hammer / build costs", _clientHammerPreference, flag2 ? new bool?(_serverEnabled && _serverHammerBuildingEnabled) : ((bool?)null)); DrawClientPreferenceToggle("Use linked smelter / processor feeding", _clientProcessorPreference, flag2 ? new bool?(_serverEnabled && _serverProcessorFeedingEnabled) : ((bool?)null)); if (flag2) { GUILayout.Space(4f); GUILayout.Label($"Server linked-storage radius: {_serverRadius:0.#} m", val, Array.Empty<GUILayoutOption>()); } } GUILayout.Space(8f); GUILayout.Label("Local display / diagnostics", val2, Array.Empty<GUILayoutOption>()); DrawLocalToggle("Show Available/Required resource counters", _showAvailableResourceCounts); DrawLocalToggle("Debug logging", _debugLogging); GUILayout.Space(8f); GUILayout.Label($"Open / close: {_settingsMenuShortcut.Value}. This menu does not pause the game.", val, Array.Empty<GUILayoutOption>()); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (!flag && GUILayout.Button("Reset local preferences", Array.Empty<GUILayoutOption>())) { _clientMasterPreference.Value = true; _clientCraftingPreference.Value = true; _clientHammerPreference.Value = true; _clientProcessorPreference.Value = true; _showAvailableResourceCounts.Value = true; _debugLogging.Value = false; SaveGuiConfigAndRefresh(broadcastServerConfig: false); } GUILayout.FlexibleSpace(); if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { SetSettingsWindowOpen(open: false); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUILayout.EndScrollView(); GUI.DragWindow(new Rect(0f, 0f, 10000f, 28f)); } private void DrawServerGameplayToggle(string label, ConfigEntry<bool> entry) { if (entry != null) { bool flag = GUILayout.Toggle(entry.Value, label, Array.Empty<GUILayoutOption>()); if (flag != entry.Value) { entry.Value = flag; SaveGuiConfigAndRefresh(broadcastServerConfig: true); } } } private void DrawClientPreferenceToggle(string label, ConfigEntry<bool> entry, bool? serverAllowed) { if (entry != null) { string text = ((!serverAllowed.HasValue) ? string.Empty : (serverAllowed.Value ? " [server allows]" : " [server disabled]")); bool flag = GUILayout.Toggle(entry.Value, label + text, Array.Empty<GUILayoutOption>()); if (flag != entry.Value) { entry.Value = flag; SaveGuiConfigAndRefresh(broadcastServerConfig: false); } } } private void DrawLocalToggle(string label, ConfigEntry<bool> entry) { if (entry != null) { bool flag = GUILayout.Toggle(entry.Value, label, Array.Empty<GUILayoutOption>()); if (flag != entry.Value) { entry.Value = flag; SaveGuiConfigAndRefresh(broadcastServerConfig: false); } } } private void SaveGuiConfigAndRefresh(bool broadcastServerConfig) { try { ((BaseUnityPlugin)this).Config.Save(); ClearContainerCache(); if (broadcastServerConfig) { BroadcastServerConfig(); } } catch (Exception ex) { LogRuntimeFault("settings-gui-save", "CraftingStorageLink could not persist a GUI setting: " + ex.GetType().Name + ": " + ex.Message); } } private void SetSettingsWindowOpen(bool open) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (_settingsWindowOpen != open) { _settingsWindowOpen = open; if (open) { _savedCursorVisible = Cursor.visible; _savedCursorLockMode = Cursor.lockState; Cursor.visible = true; Cursor.lockState = (CursorLockMode)0; } else { Cursor.visible = _savedCursorVisible; Cursor.lockState = _savedCursorLockMode; } } } private static FieldInfo FindFieldNoWarning(Type type, string name) { if (type == null || string.IsNullOrEmpty(name)) { return null; } return type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } private static void BroadcastServerConfig() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer() || _znetGetPeersMethod == null) { return; } try { if (!(_znetGetPeersMethod.Invoke(instance, null) is IEnumerable enumerable)) { return; } int num = 0; foreach (object item in enumerable) { ZNetPeer val = (ZNetPeer)((item is ZNetPeer) ? item : null); if (val != null && val.m_rpc != null) { val.m_rpc.Invoke("CSL_Config_v1", new object[1] { BuildServerConfigPackage() }); num++; } } DebugLog($"Broadcast updated CraftingStorageLink server settings to {num} connected peer(s)."); } catch (Exception ex) { LogRuntimeFault("config-broadcast", "CraftingStorageLink could not broadcast updated server settings: " + ex.GetType().Name + ": " + ex.Message); } } private void LateUpdate() { Stack<LinkContext> contextStack = _contextStack; if (contextStack != null && contextStack.Count > 0) { int count = contextStack.Count; contextStack.Clear(); ClearContainerCache(); LogRuntimeFault("context-leak", $"CraftingStorageLink recovered {count} stale runtime context frame(s) at end of frame. " + "This usually indicates another Harmony patch interrupted a crafting/build UI call."); } Player localPlayer = Player.m_localPlayer; int num = (((Object)(object)localPlayer != (Object)null) ? ((Object)localPlayer).GetInstanceID() : 0); if (num != _lastLocalPlayerInstanceId) { _lastLocalPlayerInstanceId = num; ResetContainerRegistry(); if (_contextStack != null) { _contextStack.Clear(); } DebugLog($"Runtime state and live-container registry reset for local-player lifecycle change (instance {num})."); } } private static void LogRuntimeFault(string key, string message) { try { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!_runtimeFaultLogTimes.TryGetValue(key, out var value) || !(realtimeSinceStartup - value < 10f)) { _runtimeFaultLogTimes[key] = realtimeSinceStartup; ManualLogSource log = _log; if (log != null) { log.LogWarning((object)message); } } } catch { } } private static string SafeContainerName(Container container) { if ((Object)(object)container == (Object)null) { return "<null>"; } try { return string.IsNullOrEmpty(((Object)container).name) ? "<unnamed container>" : ((Object)container).name; } catch { return "<unavailable container>"; } } private void OnDestroy() { if (_settingsWindowOpen) { SetSettingsWindowOpen(open: false); } if (_harmony != null) { _harmony.UnpatchSelf(); _harmony = null; } ResetContainerRegistry(); } private static bool IsLocalEnabled(Player player) { if (_enabled == null || !EffectiveEnabled || (Object)(object)player == (Object)null) { return false; } if ((Object)(object)Player.m_localPlayer != (Object)null) { return (Object)(object)Player.m_localPlayer == (Object)(object)player; } return false; } private static Recipe GetCraftRecipe(InventoryGui gui) { if ((Object)(object)gui == (Object)null) { return null; } if (_inventoryGuiCraftRecipeField != null) { object? value = _inventoryGuiCraftRecipeField.GetValue(gui); Recipe val = (Recipe)((value is Recipe) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } if (_inventoryGuiSelectedRecipeField == null) { return null; } try { object value2 = _inventoryGuiSelectedRecipeField.GetValue(gui); if (value2 == null) { return null; } Recipe val2 = (Recipe)((value2 is Recipe) ? value2 : null); if (val2 != null) { return val2; } Type type = value2.GetType(); string[] array = new string[5] { "Recipe", "recipe", "m_recipe", "Key", "Item1" }; foreach (string name in array) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && typeof(Recipe).IsAssignableFrom(property.PropertyType)) { object? value3 = property.GetValue(value2, null); Recipe val3 = (Recipe)((value3 is Recipe) ? value3 : null); if ((Object)(object)val3 != (Object)null) { return val3; } } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && typeof(Recipe).IsAssignableFrom(field.FieldType)) { object? value4 = field.GetValue(value2); Recipe val4 = (Recipe)((value4 is Recipe) ? value4 : null); if ((Object)(object)val4 != (Object)null) { return val4; } } } PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (typeof(Recipe).IsAssignableFrom(propertyInfo.PropertyType) && propertyInfo.GetIndexParameters().Length == 0) { object? value5 = propertyInfo.GetValue(value2, null); Recipe val5 = (Recipe)((value5 is Recipe) ? value5 : null); if ((Object)(object)val5 != (Object)null) { return val5; } } } FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (typeof(Recipe).IsAssignableFrom(fieldInfo.FieldType)) { object? value6 = fieldInfo.GetValue(value2); Recipe val6 = (Recipe)((value6 is Recipe) ? value6 : null); if ((Object)(object)val6 != (Object)null) { return val6; } } } return null; } catch (Exception ex) { DebugLog("Could not resolve InventoryGui.m_selectedRecipe: " + ex.Message); return null; } } private static CraftingStation GetActiveStation(Player player, Recipe recipe) { if (!IsLocalEnabled(player) || !EffectiveCraftingEnabled || (Object)(object)recipe == (Object)null) { return null; } CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation == (Object)null) { return null; } return currentCraftingStation; } private static CraftingStation GetBuildStation(Player player, Piece piece) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (!IsLocalEnabled(player) || !EffectiveHammerBuildingEnabled || (Object)(object)piece == (Object)null || (Object)(object)piece.m_craftingStation == (Object)null) { return null; } return CraftingStation.HaveBuildStationInRange(piece.m_craftingStation.m_name, ((Component)player).transform.position); } private static bool SupportsPiece(Piece piece) { if ((Object)(object)piece != (Object)null && piece.m_resources != null) { return piece.m_resources.Length != 0; } return false; } private static bool SupportsRecipe(Recipe recipe) { if ((Object)(object)recipe != (Object)null) { return !recipe.m_requireOnlyOneIngredient; } return false; } private static ContextGuard BeginRequirementCheck(Player player, Recipe recipe, bool discover) { LinkContext context = null; if (!discover && SupportsRecipe(recipe)) { CraftingStation activeStation = GetActiveStation(player, recipe); if ((Object)(object)activeStation != (Object)null) { context = new LinkContext(LinkContextKind.RecipeRequirement, player, activeStation, recipe); } } return PushContext(context, LinkContextKind.RecipeRequirement); } private static ContextGuard BeginRequirementDisplay(InventoryGui gui, Player player) { LinkContext context = null; if (IsLocalEnabled(player) && EffectiveCraftingEnabled) { Recipe craftRecipe = GetCraftRecipe(gui); if ((Object)(object)craftRecipe == (Object)null || SupportsRecipe(craftRecipe)) { CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation != (Object)null) { context = new LinkContext(LinkContextKind.RecipeDisplay, player, currentCraftingStation, craftRecipe); } } } return PushContext(context, LinkContextKind.RecipeDisplay); } private static ContextGuard BeginCrafting(InventoryGui gui, Player player) { LinkContext context = null; Recipe craftRecipe = GetCraftRecipe(gui); if (EffectiveCraftingEnabled && SupportsRecipe(craftRecipe)) { CraftingStation activeStation = GetActiveStation(player, craftRecipe); if ((Object)(object)activeStation != (Object)null) { context = new LinkContext(LinkContextKind.Crafting, player, activeStation, craftRecipe); } } return PushContext(context, LinkContextKind.Crafting); } private static LinkContext CreateBuildContext(Player player, Piece piece, LinkContextKind kind, RequirementMode? mode = null) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Invalid comparison between Unknown and I4 if (!IsLocalEnabled(player) || !EffectiveHammerBuildingEnabled || !SupportsPiece(piece)) { return null; } if (mode.HasValue && (int)mode.Value == 1) { return null; } CraftingStation buildStation = GetBuildStation(player, piece); if ((Object)(object)piece.m_craftingStation != (Object)null && (Object)(object)buildStation == (Object)null) { return null; } return new LinkContext(kind, player, buildStation, piece); } private static ContextGuard BeginBuildRequirementCheck(Player player, Piece piece, RequirementMode mode) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) return PushContext(CreateBuildContext(player, piece, LinkContextKind.BuildRequirement, mode), LinkContextKind.BuildRequirement); } private static ContextGuard BeginPlacement(Player player) { PieceTable val = (((Object)(object)player != (Object)null) ? player.GetBuildTool() : null); Piece piece = (((Object)(object)val != (Object)null) ? val.GetSelectedPiece() : null); return PushContext(CreateBuildContext(player, piece, LinkContextKind.Placement), LinkContextKind.Placement); } private static Piece ResolveBuildHudPiece(Player player, object[] args) { Piece val = null; if (args != null) { foreach (object obj in args) { val = (Piece)((obj is Piece) ? obj : null); if ((Object)(object)val != (Object)null) { return val; } } } if ((Object)(object)player != (Object)null) { PieceTable buildTool = player.GetBuildTool(); val = (((Object)(object)buildTool != (Object)null) ? buildTool.GetSelectedPiece() : null); } return val; } private static ContextGuard BeginBuildHudDisplay(Player player, object[] args) { Piece piece = ResolveBuildHudPiece(player, args); return PushContext(CreateBuildContext(player, piece, LinkContextKind.BuildHudDisplay), LinkContextKind.BuildHudDisplay); } private static string FormatAvailableRequirementCount(int available, int required) { return Math.Max(0, available) + "/" + Math.Max(0, required); } private static TMP_Text FindRequirementAmountText(Transform row) { if ((Object)(object)row == (Object)null) { return null; } Transform val = row.Find("res_amount"); TMP_Text val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<TMP_Text>() : null); if ((Object)(object)val2 != (Object)null) { return val2; } TMP_Text[] componentsInChildren = ((Component)row).GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && (Object)(object)((Component)val3).gameObject != (Object)null && string.Equals(((Object)((Component)val3).gameObject).name, "res_amount", StringComparison.OrdinalIgnoreCase)) { return val3; } } return null; } private static void SetRequirementCounterText(TMP_Text amountText, int available, int required) { if (!((Object)(object)amountText == (Object)null)) { amountText.text = FormatAvailableRequirementCount(available, required); amountText.enableAutoSizing = true; amountText.fontSizeMin = Mathf.Min((amountText.fontSizeMin > 0f) ? amountText.fontSizeMin : 10f, 10f); amountText.fontSizeMax = Mathf.Max(amountText.fontSize, amountText.fontSizeMax); amountText.overflowMode = (TextOverflowModes)0; } } private static void StabilizeCraftRequirementDisplay(InventoryGui gui, Transform row, Requirement requirement, Player player, int qualityLevel, int craftMultiplier) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) if (!ShowAvailableResourceCounts || (Object)(object)gui == (Object)null || (Object)(object)row == (Object)null || !IsLocalEnabled(player) || !EffectiveCraftingEnabled || requirement == null || (Object)(object)requirement.m_resItem == (Object)null || requirement.m_resItem.m_itemData == null || requirement.m_resItem.m_itemData.m_shared == null) { return; } Recipe craftRecipe = GetCraftRecipe(gui); if ((Object)(object)craftRecipe != (Object)null && !SupportsRecipe(craftRecipe)) { return; } CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation == (Object)null) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return; } int num = requirement.GetAmount(qualityLevel) * craftMultiplier; if (num <= 0) { return; } string name = requirement.m_resItem.m_itemData.m_shared.m_name; int num2 = inventory.CountItems(name, -1, true); int num3 = CountLinkedItem(player, currentCraftingStation, ((Component)currentCraftingStation).transform.position, name, -1, matchWorldLevel: true, int.MaxValue); int num4 = num2 + num3; TMP_Text val = FindRequirementAmountText(row); if ((Object)(object)val != (Object)null) { SetRequirementCounterText(val, num4, num); if (num4 >= num) { ((Graphic)val).color = Color.white; } } if (num4 >= num) { Transform val2 = row.Find("res_icon"); Image val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent<Image>() : null); if ((Object)(object)val3 != (Object)null) { ((Behaviour)val3).enabled = true; ((Graphic)val3).color = Color.white; } } } private static void StabilizeBuildRequirementDisplay(Hud hud, Player player, Piece piece) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)hud == (Object)null || !IsLocalEnabled(player) || !EffectiveHammerBuildingEnabled || !SupportsPiece(piece)) { return; } CraftingStation buildStation = GetBuildStation(player, piece); if ((Object)(object)piece.m_craftingStation != (Object)null && (Object)(object)buildStation == (Object)null) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null || hud.m_requirementItems == null) { return; } Vector3 origin = (((Object)(object)buildStation != (Object)null) ? ((Component)buildStation).transform.position : ((Component)player).transform.position); int num = Math.Min(piece.m_resources.Length, hud.m_requirementItems.Length); for (int i = 0; i < num; i++) { Requirement val = piece.m_resources[i]; if (val == null || (Object)(object)val.m_resItem == (Object)null || val.m_resItem.m_itemData == null || val.m_resItem.m_itemData.m_shared == null) { continue; } int amount = val.GetAmount(0); if (amount <= 0) { continue; } string name = val.m_resItem.m_itemData.m_shared.m_name; int num2 = inventory.CountItems(name, -1, true); if (ShowAvailableResourceCounts) { num2 += CountLinkedItem(player, buildStation, origin, name, -1, matchWorldLevel: true, int.MaxValue); } else if (num2 < amount) { num2 += CountLinkedItem(player, buildStation, origin, name, -1, matchWorldLevel: true, amount - num2); } GameObject val2 = hud.m_requirementItems[i]; if ((Object)(object)val2 == (Object)null) { continue; } TMP_Text val3 = FindRequirementAmountText(val2.transform); if ((Object)(object)val3 != (Object)null && ShowAvailableResourceCounts) { SetRequirementCounterText(val3, num2, amount); } if (num2 >= amount) { if ((Object)(object)val3 != (Object)null) { ((Graphic)val3).color = Color.white; } Transform val4 = val2.transform.Find("res_icon"); Image val5 = (((Object)(object)val4 != (Object)null) ? ((Component)val4).GetComponent<Image>() : null); if ((Object)(object)val5 != (Object)null) { ((Behaviour)val5).enabled = true; ((Graphic)val5).color = Color.white; } } } } private static bool IsBuildContext(LinkContext context) { if (context == null) { return false; } if (context.Kind != LinkContextKind.BuildRequirement && context.Kind != LinkContextKind.BuildHudDisplay) { return context.Kind == LinkContextKind.Placement; } return true; } private static bool ContextAllowsLinkedCounting(LinkContext context) { if (context == null || !context.Enabled) { return false; } if (context.Kind != LinkContextKind.RecipeRequirement && context.Kind != LinkContextKind.RecipeDisplay) { return context.Kind == LinkContextKind.BuildRequirement; } return true; } private static void AddLinkedCount(Inventory inventory, string itemName, int quality, bool matchWorldLevel, ref int result) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) try { LinkContext currentContext = CurrentContext; if (ContextAllowsLinkedCounting(currentContext) && !((Object)(object)currentContext.Player == (Object)null) && currentContext.HasOrigin && inventory != null && inventory == currentContext.PlayerInventory) { result += CountLinkedItem(currentContext.Player, currentContext.Station, currentContext.Origin, itemName, quality, matchWorldLevel, int.MaxValue); } } catch (Exception ex) { LogRuntimeFault("linked-count-postfix", "CraftingStorageLink recovered from a linked-count failure: " + ex.GetType().Name + ": " + ex.Message); ClearContainerCache(); } } private static int CountLinkedItem(Player player, CraftingStation station, Vector3 origin, string itemName, int quality, bool matchWorldLevel, int stopAt) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(itemName) || !IsLocalEnabled(player)) { return 0; } List<ContainerCandidate> list; try { list = FindContainers(player, station, origin); } catch (Exception ex) { LogRuntimeFault("container-discovery-count", "CraftingStorageLink recovered from container discovery while counting " + itemName + ": " + ex.GetType().Name + ": " + ex.Message); ClearContainerCache(); return 0; } if (_linkedCountCacheFrame != Time.frameCount) { _linkedCountCacheFrame = Time.frameCount; _linkedCountFrameCache.Clear(); } string key = itemName + "\u001f" + quality + "\u001f" + (matchWorldLevel ? "1" : "0"); if (_linkedCountFrameCache.TryGetValue(key, out var value)) { if (value.Complete) { if (stopAt != int.MaxValue) { return Math.Min(value.Count, stopAt); } return value.Count; } if (value.Count >= stopAt) { return stopAt; } } int num = 0; bool flag = true; for (int i = 0; i < list.Count; i++) { Container container = list[i].Container; try { Inventory loadedInventory = GetLoadedInventory(container); if (loadedInventory == null) { DebugLog("Linked container '" + SafeContainerName(container) + "' had no readable inventory while counting " + itemName + "."); continue; } int num2 = loadedInventory.CountItems(itemName, quality, matchWorldLevel); if (num2 > 0) { DebugLog($"Counted {num2}x {itemName} in '{SafeContainerName(container)}'."); } num += num2; if (stopAt != int.MaxValue && num >= stopAt) { flag = false; break; } } catch (Exception ex2) { LogRuntimeFault("count-container-" + SafeContainerName(container), "CraftingStorageLink skipped a transient container while counting " + itemName + ": " + ex2.GetType().Name + ": " + ex2.Message); } } if (_linkedCountFrameCache.TryGetValue(key, out var value2) && (value2.Complete || (!flag && value2.Count >= num))) { if (stopAt != int.MaxValue) { return Math.Min(value2.Count, stopAt); } return value2.Count; } _linkedCountFrameCache[key] = new LinkedCountFrameEntry(num, flag); if (stopAt != int.MaxValue) { return Math.Min(num, stopAt); } return num; } private static bool RequirementAppliesToTransaction(Requirement requirement, LinkContext context) { if (requirement == null || context == null) { return false; } if (context.Kind == LinkContextKind.Crafting && ((Object)(object)context.Station != (Object)null && context.Station.m_upgrader) != requirement.m_upgraderResource) { return false; } return true; } private static int RawCountItems(Inventory inventory, string itemName, int quality, bool matchWorldLevel) { if (inventory == null || string.IsNullOrEmpty(itemName)) { return 0; } int num = 0; List<ItemData> allItems = inventory.GetAllItems(); if (allItems == null) { return 0; } for (int i = 0; i < allItems.Count; i++) { ItemData val = allItems[i]; if (val != null && val.m_shared != null && !(val.m_shared.m_name != itemName) && (quality < 0 || val.m_quality == quality) && (!matchWorldLevel || val.m_worldLevel >= Game.m_worldLevel)) { num += Math.Max(0, val.m_stack); } } return num; } private static int CountTransactionTotal(Player player, CraftingStation station, Vector3 origin, string itemName, int quality) { //IL_001d: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0; } int num = RawCountItems(((Humanoid)player).GetInventory(), itemName, quality, matchWorldLevel: true); List<ContainerCandidate> list = FindContainers(player, station, origin); for (int i = 0; i < list.Count; i++) { Container container = list[i].Container; if (!IsEligibleContainer(player, container)) { continue; } float num2 = Mathf.Max(1f, EffectiveRadius); Vector3 val = ((Component)container).transform.position - origin; if (!(((Vector3)(ref val)).sqrMagnitude > num2 * num2)) { try { Inventory loadedInventory = GetLoadedInventory(container); num += RawCountItems(loadedInventory, itemName, quality, matchWorldLevel: true); } catch (Exception ex) { LogRuntimeFault("transaction-count-" + SafeContainerName(container), "CraftingStorageLink skipped a transient container while auditing " + itemName + ": " + ex.GetType().Name + ": " + ex.Message); } } } return num; } private static int RemoveFromPlayer(Player player, string itemName, int quality, int requested) { if ((Object)(object)player == (Object)null || requested <= 0) { return 0; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return 0; } int num = RawCountItems(inventory, itemName, quality, matchWorldLevel: true); int num2 = Math.Min(num, requested); if (num2 <= 0) { return 0; } inventory.RemoveItem(itemName, num2, quality, true); int num3 = RawCountItems(inventory, itemName, quality, matchWorldLevel: true); return Math.Max(0, num - num3); } private static PullPlan CreatePullPlan(Player player, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) LinkContext currentContext = CurrentContext; if (currentContext == null || !currentContext.Enabled || (Object)(object)currentContext.Player != (Object)(object)player || !currentContext.HasOrigin || requirements == null || requirements.Length == 0) { return null; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return null; } PullPlan pullPlan = new PullPlan(player, currentContext.Station, currentContext.Origin); foreach (Requirement val in requirements) { if (val != null && !((Object)(object)val.m_resItem == (Object)null) && RequirementAppliesToTransaction(val, currentContext)) { int num = val.GetAmount(qualityLevel) * multiplier; if (num > 0) { string name = val.m_resItem.m_itemData.m_shared.m_name; pullPlan.AddRequired(name, itemQuality, num); } } } bool flag = false; for (int j = 0; j < pullPlan.Items.Count; j++) { PullItem pullItem = pullPlan.Items[j]; int num2 = RawCountItems(inventory, pullItem.Name, pullItem.Quality, matchWorldLevel: true); if (num2 < pullItem.Required) { flag = true; } pullItem.BeforeTotal = CountTransactionTotal(player, currentContext.Station, currentContext.Origin, pullItem.Name, pullItem.Quality); DebugLog($"Transaction start {pullItem.Name}: required={pullItem.Required}, player={num2}, combined={pullItem.BeforeTotal}, multiplier={multiplier}."); } if (!flag || pullPlan.Items.Count <= 0) { return null; } return pullPlan; } private static void PayPullPlan(PullPlan plan) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) if (plan == null || !IsLocalEnabled(plan.Player)) { return; } List<ContainerCandidate> list = FindContainers(plan.Player, plan.Station, plan.Origin); for (int i = 0; i < plan.Items.Count; i++) { PullItem pullItem = plan.Items[i]; int num = CountTransactionTotal(plan.Player, plan.Station, plan.Origin, pullItem.Name, pullItem.Quality); int num2 = Math.Max(0, pullItem.BeforeTotal - num); if (num2 > pullItem.Required) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)($"CraftingStorageLink detected over-consumption of {pullItem.Name}: required={pullItem.Required}, " + $"already removed={num2}. Another mod may also be modifying Player.ConsumeResources. " + "CraftingStorageLink will not remove any additional items for this resource.")); } continue; } int num3 = pullItem.Required - num2; if (num3 <= 0) { DebugLog($"Transaction {pullItem.Name}: another path already paid {num2}/{pullItem.Required}; no linked pull needed."); continue; } int num4 = RemoveFromPlayer(plan.Player, pullItem.Name, pullItem.Quality, num3); num3 -= num4; int num5 = 0; for (int j = 0; j < list.Count; j++) { if (num3 <= 0) { break; } int num6 = PullFromContainer(plan.Player, plan.Station, plan.Origin, list[j].Container, pullItem.Name, pullItem.Quality, num3); num5 += num6; num3 -= num6; } int num7 = CountTransactionTotal(plan.Player, plan.Station, plan.Origin, pullItem.Name, pullItem.Quality); int num8 = Math.Max(0, pullItem.BeforeTotal - num7); DebugLog($"Transaction end {pullItem.Name}: required={pullItem.Required}, pre-paid={num2}, " + $"player-paid={num4}, linked-paid={num5}, observed-total={num8}, remaining={num3}."); if (num8 > pullItem.Required) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogWarning((object)($"CraftingStorageLink detected that {num8}x {pullItem.Name} disappeared during a transaction requiring only {pullItem.Required}. " + "This strongly indicates another Player.ConsumeResources patch or inventory mod is also consuming the resource.")); } } else if (num3 > 0) { ManualLogSource log3 = _log; if (log3 != null) { log3.LogWarning((object)$"CraftingStorageLink could not pull {num3}x {pullItem.Name}; a linked container may have changed during crafting."); } } } ClearContainerCache(); } private static List<ContainerCandidate> FindContainers(Player player, CraftingStation station, Vector3 origin) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) if (!EnsureContainerRegistry()) { return new List<ContainerCandidate>(); } float realtimeSinceStartup = Time.realtimeSinceStartup; Vector3 val; if ((Object)(object)_cachedPlayer == (Object)(object)player && (Object)(object)_cachedStation == (Object)(object)station && _cachedOriginSet) { val = _cachedOrigin - origin; if (((Vector3)(ref val)).sqrMagnitude <= 1f && _cachedContainers != null && realtimeSinceStartup - _cachedContainersAtRealtime <= 0.25f) { return _cachedContainers; } } _linkedCountCacheFrame = Time.frameCount; _linkedCountFrameCache.Clear(); List<ContainerCandidate> list = new List<ContainerCandidate>(); if (!IsLocalEnabled(player)) { return list; } float num = Mathf.Max(1f, EffectiveRadius); float num2 = num * num; List<Container> list2 = null; try { foreach (Container item in _containerRegistry) { try { if ((Object)(object)item == (Object)null) { if (list2 == null) { list2 = new List<Container>(); } list2.Add(item); continue; } Transform transform = ((Component)item).transform; if (!((Object)(object)transform == (Object)null)) { val = transform.position - origin; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (!(sqrMagnitude > num2) && IsEligibleContainer(player, item)) { list.Add(new ContainerCandidate(item, sqrMagnitude)); } } } catch (Exception ex) { LogRuntimeFault("scan-container-" + SafeContainerName(item), "CraftingStorageLink skipped a transient container during registry discovery: " + ex.GetType().Name + ": " + ex.Message); } } } catch (InvalidOperationException ex2) { LogRuntimeFault("container-registry-enumeration", "CraftingStorageLink live-container registry changed during enumeration; it will rebuild automatically: " + ex2.Message); ResetContainerRegistry(); return list; } if (list2 != null && list2.Count > 0) { for (int i = 0; i < list2.Count; i++) { _containerRegistry.Remove(list2[i]); } } list.Sort(delegate(ContainerCandidate a, ContainerCandidate b) { float distanceSqr = a.DistanceSqr; return distanceSqr.CompareTo(b.DistanceSqr); }); _cachedPlayer = player; _cachedStation = station; _cachedOrigin = origin; _cachedOriginSet = true; _cachedContainersAtRealtime = realtimeSinceStartup; _cachedContainers = list; string arg = (((Object)(object)station != (Object)null) ? station.m_name : "builder"); DebugLog($"Found {list.Count} linked containers within {num:0.#}m of {arg} " + $"from {_containerRegistry.Count} registered loaded containers."); return list; } private static bool EnsureContainerRegistry() { if (_containerRegistryBootstrapped) { return true; } try { Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0); _containerRegistry.Clear(); foreach (Container val in array) { if ((Object)(object)val != (Object)null) { _containerRegistry.Add(val); } } _containerRegistryBootstrapped = true; ClearContainerCache(); DebugLog($"Bootstrapped live-container registry with {_containerRegistry.Count} loaded containers. " + "Recurring scene-wide Container scans are now disabled for this player/world lifecycle."); return true; } catch (Exception ex) { LogRuntimeFault("container-registry-bootstrap", "CraftingStorageLink could not bootstrap the live-container registry; it will retry on the next linked-storage query: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static void RegisterContainer(Container container) { if ((Object)(object)container == (Object)null) { return; } try { if (_containerRegistry.Add(container)) { ClearContainerCache(); DebugLog("Registered live container '" + SafeContainerName(container) + "' " + $"(registry now {_containerRegistry.Count})."); } } catch (Exception ex) { LogRuntimeFault("container-registry-add", "CraftingStorageLink could not register a live container: " + ex.GetType().Name + ": " + ex.Message); } } private static void UnregisterContainer(Container container) { if (container == null) { return; } try { if (_containerRegistry.Remove(container)) { ClearContainerCache(); DebugLog("Unregistered container from live registry " + $"(registry now {_containerRegistry.Count})."); } } catch (Exception ex) { LogRuntimeFault("container-registry-remove", "CraftingStorageLink could not unregister a container: " + ex.GetType().Name + ": " + ex.Message); } } private static void ResetContainerRegistry() { _containerRegistry.Clear(); _containerRegistryBootstrapped = false; ClearContainerCache(); } private static bool IsEligibleContainer(Player player, Container container) { //IL_0207: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)container == (Object)null || (Object)(object)player == (Object)null) { return false; } if ((Object)(object)((Component)container).GetComponentInParent<TombStone>() != (Object)null) { DebugLog("Rejected container '" + ((Object)container).name + "': tombstone."); return false; } if (!EffectiveAllowShipContainers && (Object)(object)((Component)container).GetComponentInParent<Ship>() != (Object)null) { DebugLog("Rejected container '" + ((Object)container).name + "': ship containers disabled."); return false; } if (!EffectiveAllowCartContainers && (Object)(object)container.m_wagon != (Object)null) { DebugLog("Rejected container '" + ((Object)container).name + "': cart containers disabled."); return false; } if (EffectivePlayerBuiltOnly) { long num = 0L; Piece componentInParent = ((Component)container).GetComponentInParent<Piece>(); if ((Object)(object)componentInParent != (Object)null) { num = componentInParent.GetCreator(); } if (num == 0L) { ZNetView containerView = GetContainerView(container); if ((Object)(object)containerView != (Object)null && containerView.IsValid() && containerView.GetZDO() != null) { num = containerView.GetZDO().GetLong(StringExtensionMethods.GetStableHashCode("creator"), 0L); } } if (num == 0L) { DebugLog("Rejected container '" + ((Object)container).name + "': no player creator on Piece or container ZDO."); return false; } } bool flag = container.IsInUse() || ((Object)(object)container.m_wagon != (Object)null && container.m_wagon.InUse()); if (EffectiveSkipInUseContainers && flag && !IsLocallyOpenContainer(container)) { DebugLog("Ignoring in-use linked container '" + ((Object)container).name + "': it is not the local player's currently open container."); return false; } if (EffectiveSkipInUseContainers && flag) { DebugLog("Allowing locally open linked container '" + ((Object)container).name + "'."); } if (EffectiveRespectContainerAccess && !CheckContainerAccess(container, player.GetPlayerID())) { DebugLog("Rejected container '" + ((Object)container).name + "': access check failed."); return false; } if (EffectiveRespectWards && container.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false)) { DebugLog("Rejected container '" + ((Object)container).name + "': ward/private-area access denied."); return false; } ZNetView containerView2 = GetContainerView(container); if ((Object)(object)containerView2 == (Object)null || !containerView2.IsValid() || containerView2.GetZDO() == null) { DebugLog("Rejected container '" + ((Object)container).name + "': invalid or unavailable ZNetView/ZDO."); return false; } if (EffectiveRespectMultiUserChestIgnore && containerView2.GetZDO().GetBool("MUC_Ignore", false)) { DebugLog("Rejected container '" + ((Object)container).name + "': MUC_Ignore is set."); return false; } if (!EffectiveClaimOwnershipWhenPulling && !containerView2.IsOwner()) { DebugLog("Rejected container '" + ((Object)container).name + "': local peer is not owner and ClaimOwnershipWhenPulling=false."); return false; } return true; } catch (Exception ex) { LogRuntimeFault("eligible-container-" + SafeContainerName(container), "CraftingStorageLink skipped an unavailable container during eligibility checks: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static bool CheckContainerAccess(Container container, long playerId) { if ((Object)(object)container == (Object)null) { return false; } if (_containerCheckAccessMethod == null) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)"CraftingStorageLink could not resolve Container.CheckAccess; refusing to pull from this container while RespectContainerAccess is enabled."); } return false; } try { object obj = _containerCheckAccessMethod.Invoke(container, new object[1] { playerId }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch (Exception ex) { DebugLog("Container access check failed for '" + ((Object)container).name + "': " + ex.GetType().Name + ": " + ex.Message); return false; } } private static bool IsLocallyOpenContainer(Container container) { if ((Object)(object)container == (Object)null || (Object)(object)InventoryGui.instance == (Object)null || _inventoryGuiCurrentContainerField == null) { return false; } try { return _inventoryGuiCurrentContainerField.GetValue(InventoryGui.instance) == container; } catch { return false; } } private static ZNetView GetContainerView(Container container) { if ((Object)(object)container == (Object)null) { return null; } try { if (_containerNViewField != null) { object? value = _containerNViewField.GetValue(container); ZNetView val = (ZNetView)((value is ZNetView) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } } catch { } ZNetView component = ((Component)container).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null) { return component; } return ((Component)container).GetComponentInParent<ZNetView>(); } private static Inventory GetLoadedInventory(Container container) { if ((Object)(object)container == (Object)null) { return null; } try { Inventory inventory = container.GetInventory(); if (inventory != null) { return inventory; } _containerLoadMethod?.Invoke(container, null); return container.GetInventory(); } catch (Exception ex) { DebugLog("Could not load linked container '" + ((Object)container).name + "': " + ex.GetType().Name + ": " + ex.Message); return null; } } private static void SaveContainer(Container container) { if ((Object)(object)container == (Object)null || _containerSaveMethod == null) { return; } try { _containerSaveMethod.Invoke(container, null); } catch (Exception ex) { DebugLog("Could not explicitly save linked container '" + ((Object)container).name + "': " + ex.GetType().Name + ": " + ex.Message); } } private static int PullFromContainer(Player player, CraftingStation station, Vector3 origin, Container container, string itemName, int quality, int requested) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (requested <= 0 || !IsEligibleContainer(player, container)) { return 0; } float num = Mathf.Max(1f, EffectiveRadius); Vector3 val = ((Component)container).transform.position - origin; if (((Vector3)(ref val)).sqrMagnitude > num * num) { return 0; } try { ZNetView containerView = GetContainerView(container); if ((Object)(object)containerView == (Object)null || !containerView.IsValid()) { return 0; } if (!containerView.IsOwner()) { if (!EffectiveClaimOwnershipWhenPulling) { return 0; } containerView.ClaimOwnership(); } Inventory loadedInventory = GetLoadedInventory(container); if (loadedInventory == null) { return 0; } int num2 = RawCountItems(loadedInventory, itemName, quality, matchWorldLevel: true); int num3 = Math.Min(num2, requested); if (num3 <= 0) { return 0; } loadedInventory.RemoveItem(itemName, num3, quality, true); int num4 = RawCountItems(loadedInventory, itemName, quality, matchWorldLevel: true); int num5 = Math.Max(0, num2 - num4); if (num5 > 0) { SaveContainer(container); DebugLog($"Pulled {num5}x {itemName} from '{((Object)container).name}'."); } return num5; } catch (Exception ex) { LogRuntimeFault("pull-" + SafeContainerName(container), "CraftingStorageLink skipped container '" + SafeContainerName(container) + "' during pull: " + ex.GetType().Name + ": " + ex.Message); return 0; } } private static MethodInfo ResolveSingleItemDataMethod(Type type, string methodName) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != methodName)) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(ItemData)) { return methodInfo; } } } return null; } private static ItemData CloneSingleItem(ItemData source) { if (source == null || _itemDataCloneMethod == null) { return null; } try { object? obj = _itemDataCloneMethod.Invoke(source, null); ItemData val = (ItemData)((obj is ItemData) ? obj : null); if (val != null) { val.m_stack = 1; } return val; } catch (Exception ex) { LogRuntimeFault("processor-clone-item", "CraftingStorageLink could not clone a processor input item: " + ex.GetType().Name + ": " + ex.Message); return null; } } private static bool TryAddItemData(Inventory inventory, ItemData item) { if (inventory == null || item == null || _inventoryAddItemDataMethod == null) { return false; } try { string text = ((item.m_shared != null) ? item.m_shared.m_name : null); int quality = item.m_quality; int num = ((!string.IsNullOrEmpty(text)) ? RawCountItems(inventory, text, quality, matchWorldLevel: false) : 0); object obj = _inventoryAddItemDataMethod.Invoke(inventory, new object[1] { item }); int num2 = ((!string.IsNullOrEmpty(text)) ? RawCountItems(inventory, text, quality, matchWorldLevel: false) : num); if (obj is bool) { return (bool)obj && num2 > num; } return num2 > num; } catch (Exception ex) { LogRuntimeFault("processor-add-item", "CraftingStorageLink could not stage a processor input item in the player inventory: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static bool RemoveOneItemData(Inventory inventory, ItemData item, string itemName, int quality) { if (inventory == null || string.IsNullOrEmpty(itemName)) { return false; } try { int num = RawCountItems(inventory, itemName, quality, matchWorldLevel: false); if (num <= 0) { return false; } if (_inventoryRemoveOneItemMethod != null && item != null) { _inventoryRemoveOneItemMethod.Invoke(inventory, new object[1] { item }); } else { inventory.RemoveItem(itemName, 1, quality, false); } return RawCountItems(inventory, itemName, quality, matchWorldLevel: false) < num; } catch (Exception ex) { LogRuntimeFault("processor-remove-item", "CraftingStorageLink could not remove a staged processor input item: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static string GetItemPrefabName(ItemData item) { try { if (item == null || (Object)(object)item.m_dropPrefab == (Object)null) { return null; } return ((Object)item.m_dropPrefab).name; } catch { return null; } } private static void AddUniquePrefabName(List<string> names, ItemDrop itemDrop) { if (names != null && !((Object)(object)itemDrop == (Object)null) && !((Object)(object)((Component)itemDrop).gameObject == (Object)null)) { string name = ((Object)((Component)itemDrop).gameObject).name; if (!string.IsNullOrEmpty(name) && !names.Contains(name)) { names.Add(name); } } } private static void CollectItemDropsFromValue(object value, List<string> names, bool conversionMode) { if (value == null || names == null) { return; } ItemDrop val = (ItemDrop)((value is ItemDrop) ? value : null); if (val != null) { AddUniquePrefabName(names, val); return; } if (value is IEnumerable enumerable && !(value is string)) { foreach (object item in enumerable) { CollectItemDropsFromValue(item, names, conversionMode); } return; } Type type = value.GetType(); string[] array = ((!conversionMode) ? new string[5] { "m_item", "item", "m_fuelItem", "fuelItem", "m_from" } : new string[4] { "m_from", "from", "m_item", "item" }); for (int i = 0; i < array.Length; i++) { FieldInfo fieldInfo = AccessTools.Field(type, array[i]); if (fieldInfo == null) { continue; } try { object? value2 = fieldInfo.GetValue(value); ItemDrop val2 = (ItemDrop)((value2 is ItemDrop) ? value2 : null); if (val2 != null) { AddUniquePrefabName(names, val2); break; } } catch { } } } private static List<string> GetProcessorAcceptedPrefabNames(Smelter processor, bool fuelInteraction) { List<string> list = new List<string>(); if ((Object)(object)processor == (Object)null) { return list; } try { if (fuelInteraction) { if (_smelterFuelItemField != null) { CollectItemDropsFromValue(_smelterFuelItemField.GetValue(processor), list, conversionMode: false); } if (_smelterFuelItemsField != null) { CollectItemDropsFromValue(_smelterFuelItemsField.GetValue(processor), list, conversionMode: false); } } else { if (_smelterConversionField != null) { CollectItemDropsFromValue(_smelterConversionField.GetValue(processor), list, conversionMode: true); } if (_smelterConversionsField != null) { CollectItemDropsFromValue(_smelterConversionsField.GetValue(processor), list, conversionMode: true); } } } catch (Exception ex) { LogRuntimeFault("processor-accepted-items-" + (fuelInteraction ? "fuel" : "input"), "CraftingStorageLink could not inspect processor accepted items: " + ex.GetType().Name + ": " + ex.Message); } return list; } private static bool InventoryHasAnyProcessorItem(Inventory inventory, List<string> acceptedP