Decompiled source of NearbyCrafting v1.2.1

plugins/NearbyCrafting.dll

Decompiled a week ago
using 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}.");
				}
			}
		}
	}
}