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 NearbyCrafting v1.2.1
plugins/NearbyCrafting.dll
Decompiled a week agousing System; 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 UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("NearbyCrafting")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("NearbyCrafting")] [assembly: AssemblyTitle("NearbyCrafting")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 NearbyCrafting { [BepInPlugin("com.mikeg.valheim.nearbycrafting", "Nearby Crafting", "1.2.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class NearbyCraftingPlugin : BaseUnityPlugin { public const string PluginGuid = "com.mikeg.valheim.nearbycrafting"; public const string PluginName = "Nearby Crafting"; public const string PluginVersion = "1.2.1"; internal static ConfigEntry<float> Range; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> EnableBuilding; internal static ConfigEntry<bool> IgnoreMovingContainers; internal static ConfigEntry<bool> RequirePlayerPlacedContainer; internal static ConfigEntry<bool> FixRequirementIndicator; internal static ConfigEntry<bool> BalrondCompatibility; internal static ConfigEntry<bool> DebugLogging; internal static ConfigEntry<bool> DebugContainerDetails; internal static ConfigEntry<bool> EnableMassDeposit; internal static ConfigEntry<KeyboardShortcut> MassDepositHotkey; internal static ManualLogSource ModLogger; private Harmony? _harmony; internal static bool DebugEnabled { get { if (DebugLogging != null) { return DebugLogging.Value; } return false; } } internal static bool ContainerDebugEnabled { get { if (DebugEnabled && DebugContainerDetails != null) { return DebugContainerDetails.Value; } return false; } } private void Awake() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Expected O, but got Unknown //IL_034f: Unknown result type (might be due to invalid IL or missing references) ModLogger = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable Nearby Crafting."); EnableBuilding = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableBuilding", true, "Allow building pieces to use materials from nearby containers."); Range = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ContainerRange", 20f, new ConfigDescription("Maximum distance in metres from the player to a container.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>())); IgnoreMovingContainers = ((BaseUnityPlugin)this).Config.Bind<bool>("Containers", "IgnoreMovingContainers", true, "Ignore carts and ships. Recommended for network safety."); RequirePlayerPlacedContainer = ((BaseUnityPlugin)this).Config.Bind<bool>("Containers", "RequirePlayerPlacedContainer", true, "Only use containers attached to a Piece (normally player-built storage), avoiding most world loot chests."); FixRequirementIndicator = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "FixRequirementIndicator", true, "Treat nearby-container materials as available when coloring crafting requirement indicators."); BalrondCompatibility = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "BalrondConstructions", true, "Recognize storage prefabs where Container and Piece are siblings under the same prefab root (used by BalrondConstructions and some other build mods)."); DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLogging", false, "Write Nearby Crafting diagnostic information to BepInEx LogOutput.log."); DebugContainerDetails = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugContainerDetails", false, "When DebugLogging is enabled, include per-container cache/scan details. This can be noisy."); EnableMassDeposit = ((BaseUnityPlugin)this).Config.Bind<bool>("Quick Deposit", "Enabled", true, "Enable the nearby mass quick-deposit hotkey."); MassDepositHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Quick Deposit", "Hotkey", new KeyboardShortcut((KeyCode)287, Array.Empty<KeyCode>()), "Hotkey used to quick-deposit matching inventory stacks into all eligible nearby containers. Default: F6."); Range.SettingChanged += OnContainerSettingChanged; IgnoreMovingContainers.SettingChanged += OnContainerSettingChanged; RequirePlayerPlacedContainer.SettingChanged += OnContainerSettingChanged; BalrondCompatibility.SettingChanged += OnContainerSettingChanged; Enabled.SettingChanged += OnEnabledChanged; _harmony = new Harmony("com.mikeg.valheim.nearbycrafting"); try { _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Nearby Crafting 1.2.1 loaded."); if (DebugEnabled) { Debug($"Config: Enabled={Enabled.Value}, EnableBuilding={EnableBuilding.Value}, Range={Range.Value:0.##}m, " + $"RequirePlayerPlacedContainer={RequirePlayerPlacedContainer.Value}, " + $"IgnoreMovingContainers={IgnoreMovingContainers.Value}, " + $"FixRequirementIndicator={FixRequirementIndicator.Value}, " + $"BalrondCompatibility={BalrondCompatibility.Value}, " + $"EnableMassDeposit={EnableMassDeposit.Value}, MassDepositHotkey={MassDepositHotkey.Value}, " + $"DebugContainerDetails={DebugContainerDetails.Value}"); } } catch (Exception ex) { try { _harmony.UnpatchSelf(); } catch { } Enabled.Value = false; ((BaseUnityPlugin)this).Logger.LogError((object)"Nearby Crafting failed to apply its Harmony patches and has been disabled."); ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } private static void OnContainerSettingChanged(object? sender, EventArgs e) { NearbyContainers.InvalidateAll(); if (DebugEnabled) { Debug("Container-related configuration changed; caches invalidated."); } } private static void OnEnabledChanged(object? sender, EventArgs e) { NearbyContainers.InvalidateAll(); RequirementUiCache.Clear(); BuildingPlacementState.Clear(); RecipeRequirementContext.Clear(); RecipeConsumptionRules.Clear(); } internal static void Debug(string message) { if (DebugEnabled) { ModLogger.LogInfo((object)("[DEBUG] " + message)); } } internal static void DebugContainer(string message) { if (ContainerDebugEnabled) { ModLogger.LogInfo((object)("[DEBUG-CONTAINER] " + message)); } } private void Update() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (Enabled == null || !Enabled.Value || EnableMassDeposit == null || !EnableMassDeposit.Value || MassDepositHotkey == null) { return; } KeyboardShortcut value = MassDepositHotkey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { MassQuickDeposit(localPlayer); } } } private static void MassQuickDeposit(Player player) { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return; } List<Container> list = NearbyContainers.Get(player); if (list.Count == 0) { if (DebugEnabled) { Debug("Mass quick-deposit: no eligible nearby containers."); } return; } int num = 0; int num2 = 0; int num3 = 0; for (int i = 0; i < list.Count; i++) { Container val = list[i]; if ((Object)(object)val == (Object)null) { continue; } try { Inventory inventory2 = val.GetInventory(); if (inventory2 != null && inventory2 != inventory) { int num4 = QuickDepositIntoContainer(inventory, inventory2); num++; num3 += num4; if (num4 > 0) { num2++; } if (ContainerDebugEnabled) { DebugContainer($"Mass quick-deposit processed '{((Object)val).name}': moved={num4}."); } } } catch (Exception ex) { if (DebugEnabled) { Debug("Mass quick-deposit skipped '" + ((Object)val).name + "' after " + ex.GetType().Name + ": " + ex.Message); } } } NearbyContainers.InvalidateCounts(); if (DebugEnabled) { Debug($"Mass quick-deposit completed across {num} eligible container(s); receiving={num2}; moved={num3} total item(s)."); } string text = ((num3 > 0) ? string.Format("Quick Deposit: {0} item{1} deposited into {2} chest{3}", num3, (num3 == 1) ? "" : "s", num2, (num2 == 1) ? "" : "s") : "Quick Deposit: nothing to deposit"); ((Character)player).Message((MessageType)2, text, 0, (Sprite)null, false); } private static int QuickDepositIntoContainer(Inventory sourceInventory, Inventory targetInventory) { int num = 0; List<ItemData> list = new List<ItemData>(targetInventory.GetAllItems()); List<ItemData> list2 = new List<ItemData>(sourceInventory.GetAllItems()); for (int i = 0; i < list2.Count; i++) { ItemData val = list2[i]; if (val == null || val.m_shared == null || val.m_stack <= 0 || val.m_shared.m_maxStackSize <= 1) { continue; } List<ItemData> list3 = new List<ItemData>(); for (int j = 0; j < list.Count; j++) { ItemData val2 = list[j]; if (CanQuickStackTogether(val, val2)) { list3.Add(val2); } } if (list3.Count == 0) { continue; } for (int k = 0; k < list3.Count; k++) { if (val.m_stack <= 0) { break; } ItemData val3 = list3[k]; if (val3 == null || val3.m_shared == null) { continue; } int num2 = val3.m_shared.m_maxStackSize - val3.m_stack; if (num2 > 0) { int num3 = Math.Min(val.m_stack, num2); int stack = val.m_stack; targetInventory.MoveItemToThis(sourceInventory, val, num3, val3.m_gridPos.x, val3.m_gridPos.y); int num4 = Math.Max(0, stack - val.m_stack); num += num4; if (ContainerDebugEnabled && num4 > 0) { DebugContainer($"Quick-deposit merged {num4}x '{val.m_shared.m_name}' into existing stack at {val3.m_gridPos.x},{val3.m_gridPos.y}."); } } } while (val.m_stack > 0) { int num5 = -1; int num6 = -1; for (int l = 0; l < targetInventory.GetHeight(); l++) { if (num5 >= 0) { break; } for (int m = 0; m < targetInventory.GetWidth(); m++) { if (targetInventory.GetItemAt(m, l) == null) { num5 = m; num6 = l; break; } } } if (num5 < 0 || num6 < 0) { break; } int num7 = Math.Min(val.m_stack, val.m_shared.m_maxStackSize); int stack2 = val.m_stack; targetInventory.MoveItemToThis(sourceInventory, val, num7, num5, num6); int num8 = Math.Max(0, stack2 - val.m_stack); if (num8 <= 0) { break; } num += num8; if (ContainerDebugEnabled) { DebugContainer($"Quick-deposit moved {num8}x '{val.m_shared.m_name}' into empty slot {num5},{num6}."); } } } return num; } private static bool CanQuickStackTogether(ItemData source, ItemData target) { if (source == null || target == null || source.m_shared == null || target.m_shared == null) { return false; } if (source.m_shared.m_name != target.m_shared.m_name || source.m_quality != target.m_quality || source.m_variant != target.m_variant || source.m_worldLevel != target.m_worldLevel) { return false; } if (source.m_customData.Count != target.m_customData.Count) { return false; } foreach (KeyValuePair<string, string> customDatum in source.m_customData) { if (!target.m_customData.TryGetValue(customDatum.Key, out var value) || value != customDatum.Value) { return false; } } return true; } private void OnDestroy() { Range.SettingChanged -= OnContainerSettingChanged; IgnoreMovingContainers.SettingChanged -= OnContainerSettingChanged; RequirePlayerPlacedContainer.SettingChanged -= OnContainerSettingChanged; BalrondCompatibility.SettingChanged -= OnContainerSettingChanged; Enabled.SettingChanged -= OnEnabledChanged; NearbyContainers.InvalidateAll(); RequirementUiCache.Clear(); BuildingPlacementState.Clear(); RecipeRequirementContext.Clear(); RecipeConsumptionRules.Clear(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class CraftingContext { internal static int Depth; internal static bool IsCrafting => Depth > 0; } internal static class RecipeRequirementContext { internal static Player? Player; internal static Inventory? PlayerInventory; internal static int Depth; internal static bool IsActive { get { if (Depth > 0 && (Object)(object)Player != (Object)null) { return PlayerInventory != null; } return false; } } internal static void Begin(Player player, bool discover) { if (!discover && !((Object)(object)player == (Object)null)) { if (Depth == 0) { Player = player; PlayerInventory = ((Humanoid)player).GetInventory(); } Depth++; } } internal static void End() { if (Depth > 0) { Depth--; if (Depth == 0) { Player = null; PlayerInventory = null; } } } internal static void Clear() { Depth = 0; Player = null; PlayerInventory = null; } } internal readonly struct RecipeConsumptionKey : IEquatable<RecipeConsumptionKey> { internal readonly Requirement[] Requirements; internal readonly int QualityLevel; internal RecipeConsumptionKey(Requirement[] requirements, int qualityLevel) { Requirements = requirements; QualityLevel = qualityLevel; } public bool Equals(RecipeConsumptionKey other) { if (Requirements == other.Requirements) { return QualityLevel == other.QualityLevel; } return false; } public override bool Equals(object? obj) { if (obj is RecipeConsumptionKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (RuntimeHelpers.GetHashCode(Requirements) * 397) ^ QualityLevel; } } internal static class RecipeConsumptionRules { private static readonly Dictionary<RecipeConsumptionKey, HashSet<string>> AllowedNames = new Dictionary<RecipeConsumptionKey, HashSet<string>>(); private static HashSet<string>? _currentNames; private static Requirement[]? _currentRequirements; private static int _currentQualityLevel; internal static void Begin(Recipe recipe, int qualityLevel) { _currentRequirements = recipe?.m_resources; _currentQualityLevel = qualityLevel; _currentNames = new HashSet<string>(StringComparer.Ordinal); } internal static void Record(string name) { if (_currentNames != null && !string.IsNullOrEmpty(name)) { _currentNames.Add(name); } } internal static void Complete(bool success) { if (success && _currentRequirements != null && _currentNames != null) { RecipeConsumptionKey key = new RecipeConsumptionKey(_currentRequirements, _currentQualityLevel); AllowedNames[key] = new HashSet<string>(_currentNames, StringComparer.Ordinal); if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"Recorded {AllowedNames[key].Count} vanilla-consumed requirement name(s) " + $"for quality {_currentQualityLevel}."); } } _currentRequirements = null; _currentNames = null; _currentQualityLevel = 0; } internal static bool TryGet(Requirement[] requirements, int qualityLevel, out HashSet<string>? allowedNames) { if (requirements != null && AllowedNames.TryGetValue(new RecipeConsumptionKey(requirements, qualityLevel), out HashSet<string> value)) { allowedNames = value; return true; } allowedNames = null; return false; } internal static void Clear() { AllowedNames.Clear(); _currentRequirements = null; _currentNames = null; _currentQualityLevel = 0; } } internal static class BuildingPlacementState { private static Player? _lastPlacedPlayer; private static int _lastPlacedFrame = -1; internal static void MarkPlaced(Player player, bool placed) { if (placed && !((Object)(object)player == (Object)null)) { _lastPlacedPlayer = player; _lastPlacedFrame = Time.frameCount; if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"Successful build placement marked for frame {_lastPlacedFrame}."); } } } internal static bool WasPlacedThisFrame(Player player) { if ((Object)(object)player != (Object)null && (Object)(object)_lastPlacedPlayer == (Object)(object)player) { return _lastPlacedFrame == Time.frameCount; } return false; } internal static void Clear() { _lastPlacedPlayer = null; _lastPlacedFrame = -1; } } internal readonly struct ItemCountKey : IEquatable<ItemCountKey> { internal readonly string Name; internal readonly int Quality; internal ItemCountKey(string name, int quality) { Name = name; Quality = quality; } public bool Equals(ItemCountKey other) { if (Quality == other.Quality) { return string.Equals(Name, other.Name, StringComparison.Ordinal); } return false; } public override bool Equals(object? obj) { if (obj is ItemCountKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (((Name != null) ? StringComparer.Ordinal.GetHashCode(Name) : 0) * 397) ^ Quality; } } internal readonly struct CachedCount { internal readonly int Value; internal readonly float CreatedAt; internal readonly int ContainerGeneration; internal CachedCount(int value, float createdAt, int containerGeneration) { Value = value; CreatedAt = createdAt; ContainerGeneration = containerGeneration; } } internal readonly struct ContainerTraits { internal readonly Container Container; internal readonly bool HasPiece; internal readonly bool IsMoving; internal ContainerTraits(Container container, bool hasPiece, bool isMoving) { Container = container; HasPiece = hasPiece; IsMoving = isMoving; } } internal readonly struct ContainerDistance { internal readonly Container Container; internal readonly float DistanceSq; internal ContainerDistance(Container container, float distanceSq) { Container = container; DistanceSq = distanceSq; } } internal static class NearbyContainers { private const float ContainerCacheLifetime = 0.5f; private const float CountCacheLifetime = 0.1f; private const float MovementRefreshDistanceSq = 1f; private static readonly List<Container> CachedContainers = new List<Container>(); private static readonly List<ContainerDistance> ScanBuffer = new List<ContainerDistance>(); private static readonly Dictionary<ItemCountKey, CachedCount> CountCache = new Dictionary<ItemCountKey, CachedCount>(); private static readonly Dictionary<int, ContainerTraits> TraitCache = new Dictionary<int, ContainerTraits>(); private static Player? _cachedPlayer; private static Vector3 _cachedPlayerPosition; private static float _containerCacheCreatedAt = -1000f; private static float _cachedRange = -1f; private static bool _cachedRequirePiece; private static bool _cachedIgnoreMoving; private static bool _cachedBalrondCompatibility; private static int _containerGeneration; internal static void InvalidateAll() { CachedContainers.Clear(); ScanBuffer.Clear(); CountCache.Clear(); TraitCache.Clear(); _cachedPlayer = null; _containerCacheCreatedAt = -1000f; _cachedRange = -1f; _containerGeneration++; } internal static void InvalidateCounts() { CountCache.Clear(); } private static bool ContainerCacheIsValid(Player player, float now) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_cachedPlayer != (Object)(object)player) { return false; } if (now - _containerCacheCreatedAt >= 0.5f) { return false; } Vector3 val = ((Component)player).transform.position - _cachedPlayerPosition; if (((Vector3)(ref val)).sqrMagnitude >= 1f) { return false; } if (!Mathf.Approximately(_cachedRange, NearbyCraftingPlugin.Range.Value)) { return false; } if (_cachedRequirePiece != NearbyCraftingPlugin.RequirePlayerPlacedContainer.Value || _cachedIgnoreMoving != NearbyCraftingPlugin.IgnoreMovingContainers.Value || _cachedBalrondCompatibility != NearbyCraftingPlugin.BalrondCompatibility.Value) { return false; } return true; } internal static List<Container> Get(Player player) { if ((Object)(object)player == (Object)null) { return CachedContainers; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (ContainerCacheIsValid(player, realtimeSinceStartup)) { return CachedContainers; } RefreshContainerCache(player, realtimeSinceStartup); return CachedContainers; } private static void RefreshContainerCache(Player player, float now) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) CachedContainers.Clear(); ScanBuffer.Clear(); CountCache.Clear(); float num = Mathf.Max(0f, NearbyCraftingPlugin.Range.Value); float num2 = num * num; Vector3 position = ((Component)player).transform.position; float num3 = (NearbyCraftingPlugin.DebugEnabled ? Time.realtimeSinceStartup : 0f); Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0); foreach (Container val in array) { if ((Object)(object)val == (Object)null || !((Behaviour)val).isActiveAndEnabled) { continue; } Vector3 val2 = ((Component)val).transform.position - position; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude > num2) { if (NearbyCraftingPlugin.ContainerDebugEnabled) { NearbyCraftingPlugin.DebugContainer($"Ignoring '{((Object)val).name}': outside {num:0.0}m range."); } continue; } ContainerTraits traits = GetTraits(val); if (NearbyCraftingPlugin.RequirePlayerPlacedContainer.Value && !traits.HasPiece) { if (NearbyCraftingPlugin.ContainerDebugEnabled) { NearbyCraftingPlugin.DebugContainer("Ignoring '" + ((Object)val).name + "': not attached to a player-placeable Piece."); } continue; } if (NearbyCraftingPlugin.IgnoreMovingContainers.Value && traits.IsMoving) { if (NearbyCraftingPlugin.ContainerDebugEnabled) { NearbyCraftingPlugin.DebugContainer("Ignoring '" + ((Object)val).name + "': moving container."); } continue; } try { if (val.GetInventory() == null) { continue; } } catch (Exception ex) { if (NearbyCraftingPlugin.ContainerDebugEnabled) { NearbyCraftingPlugin.DebugContainer("Skipping '" + ((Object)val).name + "': GetInventory threw " + ex.GetType().Name + ": " + ex.Message); } continue; } ScanBuffer.Add(new ContainerDistance(val, sqrMagnitude)); if (NearbyCraftingPlugin.ContainerDebugEnabled) { NearbyCraftingPlugin.DebugContainer($"Eligible '{((Object)val).name}' at {Mathf.Sqrt(sqrMagnitude):0.0}m."); } } ScanBuffer.Sort((ContainerDistance a, ContainerDistance b) => a.DistanceSq.CompareTo(b.DistanceSq)); for (int num4 = 0; num4 < ScanBuffer.Count; num4++) { CachedContainers.Add(ScanBuffer[num4].Container); } _cachedPlayer = player; _cachedPlayerPosition = position; _containerCacheCreatedAt = now; _cachedRange = NearbyCraftingPlugin.Range.Value; _cachedRequirePiece = NearbyCraftingPlugin.RequirePlayerPlacedContainer.Value; _cachedIgnoreMoving = NearbyCraftingPlugin.IgnoreMovingContainers.Value; _cachedBalrondCompatibility = NearbyCraftingPlugin.BalrondCompatibility.Value; _containerGeneration++; if (NearbyCraftingPlugin.DebugEnabled) { float num5 = (Time.realtimeSinceStartup - num3) * 1000f; NearbyCraftingPlugin.Debug($"Container cache refreshed: scanned={array.Length}, eligible={CachedContainers.Count}, " + $"time={num5:0.00}ms."); } } private static bool HasPlayerPlacedPiece(Container container) { try { if ((Object)(object)((Component)container).GetComponentInParent<Piece>() != (Object)null) { return true; } if (!NearbyCraftingPlugin.BalrondCompatibility.Value) { return false; } Transform root = ((Component)container).transform.root; if ((Object)(object)root == (Object)null) { return false; } Piece[] componentsInChildren = ((Component)root).GetComponentsInChildren<Piece>(true); bool flag = componentsInChildren != null && componentsInChildren.Length != 0; if (flag && NearbyCraftingPlugin.ContainerDebugEnabled) { NearbyCraftingPlugin.DebugContainer("Accepted '" + ((Object)container).name + "' through sibling-Piece compatibility fallback (root='" + ((Object)root).name + "')."); } return flag; } catch (Exception ex) { if (NearbyCraftingPlugin.ContainerDebugEnabled) { NearbyCraftingPlugin.DebugContainer("Piece-layout inspection failed for '" + ((Object)container).name + "': " + ex.GetType().Name + ": " + ex.Message); } return false; } } private static ContainerTraits GetTraits(Container container) { int instanceID = ((Object)container).GetInstanceID(); if (TraitCache.TryGetValue(instanceID, out var value) && value.Container == container && (value.HasPiece || !NearbyCraftingPlugin.BalrondCompatibility.Value)) { return value; } bool hasPiece = HasPlayerPlacedPiece(container); bool isMoving = false; try { Component[] componentsInParent = ((Component)container).GetComponentsInParent<Component>(true); for (int i = 0; i < componentsInParent.Length; i++) { switch (((object)componentsInParent[i]).GetType().Name) { case "Ship": case "Vagon": case "Wagon": isMoving = true; goto end_IL_0091; } continue; end_IL_0091: break; } } catch { } ContainerTraits containerTraits = new ContainerTraits(container, hasPiece, isMoving); TraitCache[instanceID] = containerTraits; return containerTraits; } internal static int CountCached(Player player, string sharedName, int quality = -1) { List<Container> containers = Get(player); float realtimeSinceStartup = Time.realtimeSinceStartup; ItemCountKey key = new ItemCountKey(sharedName, quality); if (CountCache.TryGetValue(key, out var value) && value.ContainerGeneration == _containerGeneration && realtimeSinceStartup - value.CreatedAt < 0.1f) { return value.Value; } int num = CountAcross(player, containers, sharedName, quality); CountCache[key] = new CachedCount(num, realtimeSinceStartup, _containerGeneration); return num; } internal static int CountFresh(Player player, string sharedName, int quality = -1) { return CountAcross(player, Get(player), sharedName, quality); } private static int CountAcross(Player player, List<Container> containers, string sharedName, int quality) { Inventory inventory = ((Humanoid)player).GetInventory(); int num = ((quality >= 0) ? inventory.CountItems(sharedName, quality, true) : inventory.CountItems(sharedName, -1, true)); int num2 = num; for (int i = 0; i < containers.Count; i++) { Container val = containers[i]; if ((Object)(object)val == (Object)null) { continue; } Inventory inventory2; try { inventory2 = val.GetInventory(); } catch { continue; } if (inventory2 != null) { int num3 = ((quality >= 0) ? inventory2.CountItems(sharedName, quality, true) : inventory2.CountItems(sharedName, -1, true)); num2 += num3; if (num3 > 0 && NearbyCraftingPlugin.ContainerDebugEnabled) { NearbyCraftingPlugin.DebugContainer($"Count '{sharedName}': '{((Object)val).name}' contributes {num3}."); } } } if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"Count '{sharedName}' (quality={quality}): player={num}, " + $"nearby={num2 - num}, total={num2}."); } return num2; } internal static int CountNearbyOnly(Player player, string sharedName, int quality, bool matchWorldLevel) { if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(sharedName)) { return 0; } List<Container> list = Get(player); int num = 0; for (int i = 0; i < list.Count; i++) { Container val = list[i]; if ((Object)(object)val == (Object)null) { continue; } Inventory inventory; try { inventory = val.GetInventory(); } catch { continue; } if (inventory != null) { int num2 = inventory.CountItems(sharedName, quality, matchWorldLevel); num += num2; if (num2 > 0 && NearbyCraftingPlugin.ContainerDebugEnabled) { NearbyCraftingPlugin.DebugContainer($"Vanilla requirement count '{sharedName}': '{((Object)val).name}' contributes {num2}."); } } } return num; } private static Dictionary<string, int> AggregateRequirements(Requirement[] requirements, int qualityLevel, int multiplier, HashSet<string>? allowedNames = null) { Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); foreach (Requirement val in requirements) { if ((Object)(object)val?.m_resItem == (Object)null) { continue; } int num = val.GetAmount(qualityLevel) * multiplier; if (num <= 0) { continue; } string name = val.m_resItem.m_itemData.m_shared.m_name; int value; if (allowedNames != null && !allowedNames.Contains(name)) { if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug("Skipping conditional requirement '" + name + "' during nearby consumption."); } } else if (dictionary.TryGetValue(name, out value)) { dictionary[name] = value + num; } else { dictionary.Add(name, num); } } return dictionary; } internal static bool CanPayPlayerOnly(Player player, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier, HashSet<string>? allowedNames = null) { Dictionary<string, int> dictionary = AggregateRequirements(requirements, qualityLevel, multiplier, allowedNames); Inventory inventory = ((Humanoid)player).GetInventory(); foreach (KeyValuePair<string, int> item in dictionary) { if (((itemQuality >= 0) ? inventory.CountItems(item.Key, itemQuality, true) : inventory.CountItems(item.Key, -1, true)) < item.Value) { return false; } } return true; } internal static bool CanPayCombined(Player player, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier, HashSet<string>? allowedNames = null) { foreach (KeyValuePair<string, int> item in AggregateRequirements(requirements, qualityLevel, multiplier, allowedNames)) { int num = CountFresh(player, item.Key, itemQuality); if (num < item.Value) { if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"CanPayCombined=false for '{item.Key}': need={item.Value}, available={num}, " + $"qualityLevel={qualityLevel}, itemQuality={itemQuality}, multiplier={multiplier}."); } return false; } if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"Combined requirement OK for '{item.Key}': need={item.Value}, available={num}."); } } return true; } internal static void Pay(Player player, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier, HashSet<string>? allowedNames = null) { Dictionary<string, int> dictionary = AggregateRequirements(requirements, qualityLevel, multiplier, allowedNames); List<Container> list = Get(player); Inventory inventory = ((Humanoid)player).GetInventory(); foreach (KeyValuePair<string, int> item in dictionary) { string key = item.Key; int num = item.Value; int val = ((itemQuality >= 0) ? inventory.CountItems(key, itemQuality, true) : inventory.CountItems(key, -1, true)); int num2 = Math.Min(num, val); if (num2 > 0) { inventory.RemoveItem(key, num2, itemQuality, true); num -= num2; if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"Consumed {num2}x '{key}' from player inventory; remaining={num}."); } } for (int i = 0; i < list.Count && num > 0; i++) { Container val2 = list[i]; if ((Object)(object)val2 == (Object)null) { continue; } Inventory inventory2; try { inventory2 = val2.GetInventory(); } catch { continue; } if (inventory2 == null) { continue; } int val3 = ((itemQuality >= 0) ? inventory2.CountItems(key, itemQuality, true) : inventory2.CountItems(key, -1, true)); int num3 = Math.Min(num, val3); if (num3 > 0) { inventory2.RemoveItem(key, num3, itemQuality, true); num -= num3; if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"Consumed {num3}x '{key}' from container '{((Object)val2).name}'; remaining={num}."); } } } if (num > 0) { NearbyCraftingPlugin.ModLogger.LogWarning((object)($"Resource consumption ended with {num}x '{key}' unpaid. " + "Container contents may have changed during crafting.")); } } InvalidateCounts(); } internal static bool HaveAggregatedRequirements(Player player, Recipe recipe, int qualityLevel, int multiplier) { foreach (KeyValuePair<string, int> item in AggregateRequirements(recipe.m_resources, qualityLevel, multiplier)) { int num = CountCached(player, item.Key); if (num < item.Value) { if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"Recipe '{((Object)recipe).name}' remains unavailable: '{item.Key}' needs {item.Value}, " + $"available={num}."); } return false; } } return true; } internal static bool HaveBuildRequirementsCombined(Player player, Piece piece) { if ((Object)(object)piece == (Object)null || piece.m_resources == null) { return false; } Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); for (int i = 0; i < piece.m_resources.Length; i++) { Requirement val = piece.m_resources[i]; if (!((Object)(object)val?.m_resItem == (Object)null) && val.m_amount > 0) { string name = val.m_resItem.m_itemData.m_shared.m_name; if (dictionary.TryGetValue(name, out var value)) { dictionary[name] = value + val.m_amount; } else { dictionary.Add(name, val.m_amount); } } } foreach (KeyValuePair<string, int> item in dictionary) { int num = CountCached(player, item.Key); if (num < item.Value) { if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug("Build requirement failed for '" + ((Object)piece).name + "': '" + item.Key + "' " + $"needs={item.Value}, available={num}."); } return false; } } return true; } } internal readonly struct RequirementGraphicEntry { internal readonly Transform Root; internal readonly Graphic Graphic; internal RequirementGraphicEntry(Transform root, Graphic graphic) { Root = root; Graphic = graphic; } } internal static class RequirementUiCache { private static readonly Dictionary<int, RequirementGraphicEntry> Graphics = new Dictionary<int, RequirementGraphicEntry>(); internal static Graphic? GetAmountGraphic(Transform elementRoot) { int instanceID = ((Object)elementRoot).GetInstanceID(); if (Graphics.TryGetValue(instanceID, out var value) && value.Root == elementRoot && (Object)(object)value.Graphic != (Object)null) { return value.Graphic; } Transform val = elementRoot.Find("res_amount"); if ((Object)(object)val == (Object)null) { return null; } Graphic component = ((Component)val).GetComponent<Graphic>(); if ((Object)(object)component != (Object)null) { Graphics[instanceID] = new RequirementGraphicEntry(elementRoot, component); } return component; } internal static void Clear() { Graphics.Clear(); } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] [HarmonyAfter(new string[] { "balrond.astafaraios.BalrondConstructions" })] internal static class DoCraftingContextPatch { private static void Prefix() { if (NearbyCraftingPlugin.Enabled.Value) { CraftingContext.Depth++; if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"DoCrafting entered. CraftingContext.Depth={CraftingContext.Depth}."); } } } private static void Finalizer(Exception? __exception) { if (__exception != null) { NearbyCraftingPlugin.ModLogger.LogError((object)("DoCrafting threw " + __exception.GetType().Name + ": " + __exception.Message)); } if (NearbyCraftingPlugin.Enabled.Value && CraftingContext.Depth > 0) { CraftingContext.Depth--; if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"DoCrafting exited. CraftingContext.Depth={CraftingContext.Depth}."); } } } } [HarmonyPatch(typeof(Player), "HaveRequirementItems", new Type[] { typeof(Recipe), typeof(bool), typeof(int), typeof(int) })] [HarmonyAfter(new string[] { "balrond.astafaraios.BalrondConstructions" })] internal static class HaveRequirementItemsPatch { [HarmonyPriority(800)] private static void Prefix(Player __instance, Recipe piece, bool discover, int qualityLevel, int amount) { if (!(!NearbyCraftingPlugin.Enabled.Value || discover) && !((Object)(object)__instance == (Object)null) && !((Object)(object)piece == (Object)null)) { RecipeRequirementContext.Begin(__instance, discover); RecipeConsumptionRules.Begin(piece, qualityLevel); if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug("Vanilla recipe check with nearby counts enabled for '" + ((Object)piece).name + "' " + $"(qualityLevel={qualityLevel}, amount={amount})."); } } } [HarmonyPriority(0)] private static void Postfix(Recipe piece, bool discover, ref bool __result) { if (!(!NearbyCraftingPlugin.Enabled.Value || discover) && !((Object)(object)piece == (Object)null)) { RecipeConsumptionRules.Complete(__result); if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"Vanilla recipe check result for '{((Object)piece).name}' with nearby counts: {__result}."); } } } private static Exception? Finalizer(Exception? __exception) { if (__exception != null) { RecipeConsumptionRules.Complete(success: false); } RecipeRequirementContext.End(); return __exception; } } [HarmonyPatch(typeof(Inventory), "CountItems", new Type[] { typeof(string), typeof(int), typeof(bool) })] [HarmonyAfter(new string[] { "balrond.astafaraios.BalrondConstructions" })] internal static class InventoryCountItemsRequirementPatch { [HarmonyPriority(0)] private static void Postfix(Inventory __instance, string name, int quality, bool matchWorldLevel, ref int __result) { if (!NearbyCraftingPlugin.Enabled.Value || !RecipeRequirementContext.IsActive || __instance == null || __instance != RecipeRequirementContext.PlayerInventory) { return; } Player player = RecipeRequirementContext.Player; if ((Object)(object)player == (Object)null) { return; } RecipeConsumptionRules.Record(name); int num = NearbyContainers.CountNearbyOnly(player, name, quality, matchWorldLevel); if (num > 0) { int num2 = __result; __result += num; if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"Vanilla CountItems augmented for '{name}': player={num2}, " + $"nearby={num}, total={__result}, quality={quality}, " + $"matchWorldLevel={matchWorldLevel}."); } } } } [HarmonyPatch(typeof(Player), "TryPlacePiece", new Type[] { typeof(Piece) })] [HarmonyAfter(new string[] { "balrond.astafaraios.BalrondConstructions" })] internal static class TryPlacePiecePatch { private static void Postfix(Player __instance, bool __result) { if (NearbyCraftingPlugin.Enabled.Value && NearbyCraftingPlugin.EnableBuilding.Value) { BuildingPlacementState.MarkPlaced(__instance, __result); } } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Piece), typeof(RequirementMode) })] [HarmonyAfter(new string[] { "balrond.astafaraios.BalrondConstructions" })] internal static class HaveBuildRequirementsPatch { [HarmonyPriority(0)] private static void Postfix(Player __instance, Piece piece, RequirementMode mode, ref bool __result) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (!((!NearbyCraftingPlugin.Enabled.Value || !NearbyCraftingPlugin.EnableBuilding.Value) | __result) && !((Object)(object)__instance == (Object)null) && !((Object)(object)piece == (Object)null) && (int)mode == 0 && (!((Object)(object)piece.m_craftingStation != (Object)null) || Object.op_Implicit((Object)(object)CraftingStation.HaveBuildStationInRange(piece.m_craftingStation.m_name, ((Component)__instance).transform.position))) && NearbyContainers.HaveBuildRequirementsCombined(__instance, piece)) { __result = true; if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug("HaveRequirements overridden to TRUE for build piece '" + ((Object)piece).name + "'."); } } } } [HarmonyPatch(typeof(Player), "ConsumeResources")] [HarmonyAfter(new string[] { "balrond.astafaraios.BalrondConstructions" })] internal static class ConsumeResourcesPatch { [HarmonyPriority(700)] private static bool Prefix(Player __instance, Requirement[] requirements, int qualityLevel, int itemQuality = -1, int multiplier = 1) { bool flag = NearbyCraftingPlugin.EnableBuilding.Value && BuildingPlacementState.WasPlacedThisFrame(__instance); bool flag2 = CraftingContext.IsCrafting || flag; if (!NearbyCraftingPlugin.Enabled.Value || !flag2 || (Object)(object)__instance == (Object)null || requirements == null) { return true; } HashSet<string> allowedNames = null; if (CraftingContext.IsCrafting) { if (RecipeConsumptionRules.TryGet(requirements, qualityLevel, out HashSet<string> allowedNames2) && allowedNames2 != null) { allowedNames = allowedNames2; if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"Using vanilla-recorded requirement set for consumption: {allowedNames2.Count} name(s)."); } } else if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug("No vanilla-recorded requirement set found for this craft; using unfiltered fallback."); } } if (NearbyContainers.CanPayPlayerOnly(__instance, requirements, qualityLevel, itemQuality, multiplier, allowedNames)) { if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug("Player inventory can fully pay; using vanilla ConsumeResources (context=" + (flag ? "building" : "crafting") + ")."); } return true; } if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug("ConsumeResources needs nearby storage for " + string.Format("{0}: requirements={1}, ", flag ? "building" : "crafting", requirements.Length) + $"qualityLevel={qualityLevel}, itemQuality={itemQuality}, multiplier={multiplier}."); } if (!NearbyContainers.CanPayCombined(__instance, requirements, qualityLevel, itemQuality, multiplier, allowedNames)) { if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug("Combined pre-flight failed; falling back to vanilla without partial chest consumption."); } return true; } NearbyContainers.Pay(__instance, requirements, qualityLevel, itemQuality, multiplier, allowedNames); if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug("Nearby Crafting completed resource consumption; skipping vanilla ConsumeResources."); } return false; } } [HarmonyPatch(typeof(InventoryGui), "SetupRequirement")] [HarmonyAfter(new string[] { "balrond.astafaraios.BalrondConstructions" })] internal static class SetupRequirementDisplayPatch { [HarmonyPriority(0)] private static void Postfix(Transform elementRoot, Requirement req, Player player, bool craft, int quality) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if (!NearbyCraftingPlugin.Enabled.Value || !NearbyCraftingPlugin.FixRequirementIndicator.Value || (Object)(object)elementRoot == (Object)null || (Object)(object)req?.m_resItem == (Object)null || (Object)(object)player == (Object)null) { return; } Graphic amountGraphic = RequirementUiCache.GetAmountGraphic(elementRoot); if ((Object)(object)amountGraphic == (Object)null) { return; } Color color = amountGraphic.color; if (color == Color.white) { return; } int amount = req.GetAmount(quality); if (amount <= 0) { return; } string name = req.m_resItem.m_itemData.m_shared.m_name; int num = NearbyContainers.CountCached(player, name); if (num >= amount) { amountGraphic.color = Color.white; if (NearbyCraftingPlugin.DebugEnabled) { NearbyCraftingPlugin.Debug($"UI indicator corrected for '{name}': need={amount}, available={num}, " + $"previousColor={color}."); } } } } }