Decompiled source of RunicCrafting v1.0.0

RunicCrafting.dll

Decompiled 9 hours ago
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RunicCrafting.Domain;
using RunicCrafting.Integration;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Runic Crafting")]
[assembly: AssemblyDescription("Vanilla-preserving crafting and building from authorized nearby materials.")]
[assembly: AssemblyCompany("Chazman")]
[assembly: AssemblyProduct("Runic Crafting")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: ComVisible(false)]
[assembly: Guid("c54681ca-bb60-4d57-a83a-6d9996aa3f57")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RunicCrafting
{
	internal static class Configuration
	{
		internal static ConfigEntry<bool> Enabled { get; private set; }

		internal static ConfigEntry<bool> CraftFromContainers { get; private set; }

		internal static ConfigEntry<bool> BuildFromContainers { get; private set; }

		internal static ConfigEntry<bool> StationlessBuildFromContainers { get; private set; }

		internal static ConfigEntry<float> StationlessBuildRangeMeters { get; private set; }

		internal static ConfigEntry<string> StationlessPieceAllowList { get; private set; }

		internal static ConfigEntry<string> StationlessPieceDenyList { get; private set; }

		internal static ConfigEntry<bool> RepairAll { get; private set; }

		internal static ConfigEntry<float> RangeCapMeters { get; private set; }

		internal static ConfigEntry<int> MaximumCandidateContainers { get; private set; }

		internal static ConfigEntry<int> MaximumReturnedContainers { get; private set; }

		internal static ConfigEntry<bool> ExcludePersonalContainers { get; private set; }

		internal static ConfigEntry<bool> RequireWardAccess { get; private set; }

		internal static ConfigEntry<WorkshopPolicyKind> DefaultStationUse { get; private set; }

		internal static ConfigEntry<WorkshopPolicyKind> DefaultLocalMaterialUse { get; private set; }

		internal static ConfigEntry<bool> ShowStatusMessages { get; private set; }

		internal static ConfigEntry<bool> DetailedLogging { get; private set; }

		internal static StationlessPiecePolicy StationlessPolicy { get; private set; }

		internal static float SafeRangeCap => Math.Max(1f, Math.Min(50f, RangeCapMeters.Value));

		internal static int SafeMaximumCandidates => Math.Max(1, Math.Min(256, MaximumCandidateContainers.Value));

		internal static int SafeMaximumReturned => Math.Max(1, Math.Min(Math.Min(128, SafeMaximumCandidates), MaximumReturnedContainers.Value));

		internal static float SafeStationlessBuildRange => Math.Max(1f, Math.Min(SafeRangeCap, StationlessBuildRangeMeters.Value));

		internal static string StateSummary => "master=" + OnOff(Enabled.Value) + "; nearby crafting=" + OnOff(CraftFromContainers.Value) + "; nearby building=" + OnOff(BuildFromContainers.Value) + "; stationless building=" + OnOff(StationlessBuildFromContainers.Value) + " (player-local " + SafeStationlessBuildRange.ToString("0.#", CultureInfo.InvariantCulture) + "m); stationless allow=" + (StationlessPolicy.AllowsWildcard ? "*" : StationlessPolicy.AllowedRuleCount.ToString(CultureInfo.InvariantCulture)) + "; stationless deny=" + StationlessPolicy.DeniedRuleCount.ToString(CultureInfo.InvariantCulture) + "; Repair All=" + OnOff(RepairAll.Value) + "; station HUD=" + OnOff(ShowStatusMessages.Value) + "; range cap=" + SafeRangeCap.ToString("0.#", CultureInfo.InvariantCulture) + "m; local-material default=" + DefaultLocalMaterialUse.Value.ToString() + "; detailed logging=" + OnOff(DetailedLogging.Value) + "; input=automatic (normal craft/build/repair controls, no hotkey)";

		internal static string HudSummary
		{
			get
			{
				if (Enabled.Value)
				{
					return "Runic Crafting: nearby craft " + OnOff(CraftFromContainers.Value) + ", nearby build " + OnOff(BuildFromContainers.Value) + ", stationless build " + OnOff(StationlessBuildFromContainers.Value) + ", Repair All " + OnOff(RepairAll.Value);
				}
				return "Runic Crafting: gameplay disabled in configuration";
			}
		}

		internal static void Bind(ConfigFile config)
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Expected O, but got Unknown
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Expected O, but got Unknown
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Expected O, but got Unknown
			Enabled = config.Bind<bool>("General", "Enabled", true, "Master switch. False leaves crafting, building, and repair gameplay to vanilla while status UI may report that Runic Crafting is off.");
			CraftFromContainers = config.Bind<bool>("Materials", "CraftFromContainers", true, "Allow a player to satisfy ordinary recipe deficits from eligible nearby containers. Dedicated clients use the authenticated durable server path.");
			BuildFromContainers = config.Bind<bool>("Materials", "BuildFromContainers", true, "Allow build pieces to satisfy deficits from eligible nearby containers.");
			StationlessBuildFromContainers = config.Bind<bool>("Stationless Building", "Enabled", true, "Allow explicitly listed pieces that require no crafting station (for example fires, the basic cooking station, and the workbench itself) to use eligible containers around the player.");
			StationlessBuildRangeMeters = config.Bind<float>("Stationless Building", "PlayerLocalRangeMeters", 20f, new ConfigDescription("Player-centered range for explicitly allowed stationless pieces. Materials.RangeCapMeters remains the global upper bound.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f), Array.Empty<object>()));
			StationlessPieceAllowList = config.Bind<string>("Stationless Building", "AllowedPrefabIds", "blackmarble_pile,blackwood_stack,bone_stack,bonfire,coal_pile,fire_pit,fire_pit_iron,grausten_pile,guard_stone,piece_artisanstation,piece_cookingstation,piece_groundtorch_mist,piece_logbench01,piece_pot1,piece_pot1_cracked,piece_pot1_red,piece_pot2,piece_pot2_cracked,piece_pot2_red,piece_pot3,piece_pot3_cracked,piece_pot3_red,piece_workbench,sapling_barley,sapling_carrot,sapling_flax,sapling_jotunpuffs,sapling_magecap,sapling_onion,sapling_seedcarrot,sapling_seedonion,sapling_seedturnip,sapling_turnip,sign,skull_pile,stone_pile,treasure_pile,treasure_stack,wood_core_stack,wood_fine_stack,wood_stack,wood_yggdrasil_stack", "Comma/semicolon-separated exact Piece prefab IDs allowed to use the stationless path. Add modded prefabs here. Empty allows none; an explicit * allows all stationless prefabs.");
			StationlessPieceDenyList = config.Bind<string>("Stationless Building", "DeniedPrefabIds", string.Empty, "Comma/semicolon-separated exact Piece prefab IDs denied from the stationless path. Deny rules win over allow rules; * denies all.");
			RepairAll = config.Bind<bool>("Repair", "RepairAll", true, "One repair-button press repairs every item that vanilla currently considers repairable.");
			RangeCapMeters = config.Bind<float>("Materials", "RangeCapMeters", 20f, new ConfigDescription("Global hard cap applied after an active station's range or the configured stationless player-local range.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 50f), Array.Empty<object>()));
			MaximumCandidateContainers = config.Bind<int>("Materials", "MaximumCandidateContainers", 64, new ConfigDescription("Maximum loaded containers considered by one event-driven query.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 256), Array.Empty<object>()));
			MaximumReturnedContainers = config.Bind<int>("Materials", "MaximumReturnedContainers", 32, new ConfigDescription("Maximum eligible containers returned to one material plan.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 128), Array.Empty<object>()));
			ExcludePersonalContainers = config.Bind<bool>("Materials", "ExcludePersonalContainers", true, "Exclude Valheim personal/private container types from nearby material use.");
			RequireWardAccess = config.Bind<bool>("Materials", "RequireWardAccess", false, "Require ward access even for containers that do not normally check a guard stone. Native guard-stone checks are always honored.");
			DefaultStationUse = config.Bind<WorkshopPolicyKind>("Workshop Access", "DefaultStationUse", WorkshopPolicyKind.Everyone, "Default independent policy for using a station.");
			DefaultLocalMaterialUse = config.Bind<WorkshopPolicyKind>("Workshop Access", "DefaultLocalMaterialUse", WorkshopPolicyKind.Approved, "Default independent policy for consuming nearby materials through a station.");
			ShowStatusMessages = config.Bind<bool>("Diagnostics", "ShowStatusMessages", true, "Show a short controller-neutral HUD status when opening a station or changing a setting.");
			DetailedLogging = config.Bind<bool>("Diagnostics", "DetailedLogging", false, "Log attempts, every important early gate/no-op reason, bounded source plans, and exact denial codes. Repeated UI probes are rate-limited.");
			RefreshDerivedSettings();
		}

		internal static void RefreshDerivedSettings()
		{
			StationlessPolicy = StationlessPiecePolicy.Parse(StationlessPieceAllowList?.Value, StationlessPieceDenyList?.Value);
		}

		private static string OnOff(bool value)
		{
			if (!value)
			{
				return "OFF";
			}
			return "ON";
		}
	}
	[BepInPlugin("chazman.RunicCrafting", "Runic Crafting", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "chazman.RunicCrafting";

		public const string Name = "Runic Crafting";

		public const string Version = "1.0.0";

		private Harmony _harmony;

		private bool _configurationSubscribed;

		private string _pendingHudNotice;

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Configuration.Bind(((BaseUnityPlugin)this).Config);
			((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged;
			_configurationSubscribed = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Runic Crafting configuration: " + Configuration.StateSummary + "."));
			try
			{
				CraftingRuntime.Initialize(((BaseUnityPlugin)this).Logger);
				_harmony = new Harmony("chazman.RunicCrafting");
				_harmony.PatchAll(typeof(Plugin).Assembly);
				WorkshopAccessCommands.Initialize();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Runic Crafting v1.0.0 ready. " + Configuration.StateSummary + ". Nearby crafting and building use locally owned Valheim inventories."));
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Runic Crafting startup failed; vanilla behavior remains available: " + ex));
				Shutdown();
			}
		}

		private void Update()
		{
			string text = Interlocked.Exchange(ref _pendingHudNotice, null);
			if (text != null && Configuration.ShowStatusMessages.Value)
			{
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null)
				{
					((Character)localPlayer).Message((MessageType)1, text, 0, (Sprite)null);
				}
			}
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs arguments)
		{
			try
			{
				Configuration.RefreshDerivedSettings();
				CraftingRuntime.OnConfigurationChanged();
				CraftingDiagnostics.ResetRepeatSuppression();
				string text = ((((arguments != null) ? arguments.ChangedSetting : null) == null) ? "unknown setting" : (arguments.ChangedSetting.Definition.Section + "." + arguments.ChangedSetting.Definition.Key));
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Runic Crafting configuration changed (" + text + "): " + Configuration.StateSummary + ". Changes are active now; no restart is required."));
				CraftingDiagnostics.TraceAction("configuration-change", text, Configuration.StateSummary);
				Interlocked.Exchange(ref _pendingHudNotice, Configuration.HudSummary);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Crafting configuration refresh failed: " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void Shutdown()
		{
			if (_configurationSubscribed)
			{
				((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged;
				_configurationSubscribed = false;
			}
			Interlocked.Exchange(ref _pendingHudNotice, null);
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not remove every crafting patch: " + ex.Message));
			}
			_harmony = null;
			CraftingRuntime.Shutdown();
			CraftingDiagnostics.ResetRepeatSuppression();
			Log = null;
		}
	}
}
namespace RunicCrafting.Integration
{
	internal sealed class ContainerQueryRuntime
	{
		private readonly WorkshopAccessRuntime _workshopAccess;

		internal ContainerQueryRuntime(WorkshopAccessRuntime workshopAccess)
		{
			_workshopAccess = workshopAccess ?? throw new ArgumentNullException("workshopAccess");
		}

		internal IReadOnlyList<IMutableMaterialSource> ResolveSources(Player player, CraftingStation station, Vector3 origin, float radius, IEnumerable<MaterialRequirement> requirements, string purposeId, bool stationlessAccessAuthorized, out string reasonCode)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			List<IMutableMaterialSource> list = new List<IMutableMaterialSource>();
			reasonCode = "ok";
			if (!ValheimReflection.CanMutateLocalPlayer(player))
			{
				reasonCode = "local-player-owner-required";
				CraftingDiagnostics.TraceGate(purposeId + ":container-query", reasonCode);
				return list.AsReadOnly();
			}
			string[] resourceIds = requirements.Select((MaterialRequirement requirement) => requirement.ResourceId).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string result) => result, StringComparer.Ordinal)
				.ToArray();
			list.Add(new ValheimMaterialSource(PrincipalValue(player), ((Humanoid)player).GetInventory(), MaterialSourceKind.PlayerInventory, 0f, resourceIds, () => ValheimReflection.CanMutateLocalPlayer(player) && ((Object)(object)station == (Object)null || _workshopAccess.Evaluate(station, player, WorkshopAction.StationUse).Allowed)));
			if ((Object)(object)station == (Object)null && !stationlessAccessAuthorized)
			{
				reasonCode = "station-required-for-nearby-materials";
				CraftingDiagnostics.TraceGate(purposeId + ":container-query", reasonCode);
				return list.AsReadOnly();
			}
			if ((Object)(object)station != (Object)null)
			{
				WorkshopAccessDecision workshopAccessDecision = _workshopAccess.Evaluate(station, player, WorkshopAction.StationUse);
				if (!workshopAccessDecision.Allowed)
				{
					reasonCode = "station-use:" + workshopAccessDecision.ReasonCode;
					CraftingDiagnostics.TraceGate(purposeId + ":container-query", reasonCode);
					return list.AsReadOnly();
				}
				WorkshopAccessDecision workshopAccessDecision2 = _workshopAccess.Evaluate(station, player, WorkshopAction.LocalMaterialUse);
				if (!workshopAccessDecision2.Allowed)
				{
					reasonCode = "local-materials:" + workshopAccessDecision2.ReasonCode;
					CraftingDiagnostics.TraceGate(purposeId + ":container-query", reasonCode);
					return list.AsReadOnly();
				}
			}
			radius = Math.Max(1f, Math.Min(Configuration.SafeRangeCap, radius));
			IReadOnlyList<Container> readOnlyList = ContainerSpatialIndex.Query(origin, radius, Configuration.SafeMaximumCandidates);
			SortedDictionary<string, int> sortedDictionary = new SortedDictionary<string, int>(StringComparer.Ordinal);
			int num = 0;
			foreach (Container item in readOnlyList)
			{
				if (num >= Configuration.SafeMaximumReturned)
				{
					break;
				}
				if (!IsEligibleContainer(item, player, origin, radius, out var rejectionReason))
				{
					sortedDictionary.TryGetValue(rejectionReason, out var value);
					sortedDictionary[rejectionReason] = value + 1;
					continue;
				}
				Container captured = item;
				string sourceId = ValheimReflection.ContainerEndpointId(captured);
				Inventory inventory = captured.GetInventory();
				Vector3 val = ((Component)captured).transform.position - origin;
				list.Add(new ValheimMaterialSource(sourceId, inventory, MaterialSourceKind.NearbyContainer, ((Vector3)(ref val)).sqrMagnitude, resourceIds, delegate
				{
					//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
					bool num2;
					if (!((Object)(object)station == (Object)null))
					{
						if (_workshopAccess.Evaluate(station, player, WorkshopAction.StationUse).Allowed)
						{
							num2 = _workshopAccess.Evaluate(station, player, WorkshopAction.LocalMaterialUse).Allowed;
							goto IL_009c;
						}
					}
					else if (stationlessAccessAuthorized)
					{
						num2 = ValheimReflection.CanMutateLocalPlayer(player);
						goto IL_009c;
					}
					goto IL_00cd;
					IL_009c:
					string rejectionReason2;
					if (num2)
					{
						return IsEligibleContainer(captured, player, origin, radius, out rejectionReason2);
					}
					goto IL_00cd;
					IL_00cd:
					return false;
				}));
				num++;
			}
			reasonCode = "bounded-local-ownership";
			CraftingDiagnostics.TraceGate(purposeId + ":container-query", reasonCode, "candidates=" + readOnlyList.Count + "; selected=" + num + FormatRejections(sortedDictionary));
			return list.AsReadOnly();
		}

		private static bool IsEligibleContainer(Container container, Player player, Vector3 origin, float radius, out string rejectionReason)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			rejectionReason = "eligible";
			if ((Object)(object)container == (Object)null || !((Behaviour)container).isActiveAndEnabled)
			{
				rejectionReason = "inactive";
				return false;
			}
			if (!ValheimReflection.CanMutateLocalPlayer(player))
			{
				rejectionReason = "player-owner";
				return false;
			}
			Vector3 val = ((Component)container).transform.position - origin;
			if (((Vector3)(ref val)).sqrMagnitude > radius * radius)
			{
				rejectionReason = "out-of-range";
				return false;
			}
			if (container.IsInUse() || ((Object)(object)container.m_wagon != (Object)null && container.m_wagon.InUse()))
			{
				rejectionReason = "in-use";
				return false;
			}
			if (!container.IsOwner())
			{
				rejectionReason = "not-zdo-owner";
				return false;
			}
			if (Configuration.ExcludePersonalContainers.Value && (int)container.m_privacy == 0)
			{
				rejectionReason = "personal-excluded";
				return false;
			}
			if (!ValheimReflection.ContainerAllows(container, player.GetPlayerID()))
			{
				rejectionReason = "container-access-denied";
				return false;
			}
			if ((container.m_checkGuardStone || Configuration.RequireWardAccess.Value) && !PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false))
			{
				rejectionReason = "ward-denied";
				return false;
			}
			return container.GetInventory() != null;
		}

		private static string FormatRejections(IEnumerable<KeyValuePair<string, int>> counts)
		{
			string[] array = counts.Select((KeyValuePair<string, int> pair) => pair.Key + "=" + pair.Value).ToArray();
			if (array.Length != 0)
			{
				return "; rejected[" + string.Join(",", array) + "]";
			}
			return string.Empty;
		}

		internal static string PrincipalValue(Player player)
		{
			return "valheim.player:" + player.GetPlayerID().ToString(CultureInfo.InvariantCulture);
		}
	}
	internal static class ContainerSpatialIndex
	{
		private readonly struct Candidate
		{
			internal Container Container { get; }

			internal float DistanceSquared { get; }

			internal Candidate(Container container, float distanceSquared)
			{
				Container = container;
				DistanceSquared = distanceSquared;
			}
		}

		private readonly struct CellKey : IEquatable<CellKey>
		{
			internal int X { get; }

			internal int Z { get; }

			internal CellKey(int x, int z)
			{
				X = x;
				Z = z;
			}

			internal static CellKey From(Vector3 position)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				return new CellKey(Mathf.FloorToInt(position.x / 10f), Mathf.FloorToInt(position.z / 10f));
			}

			public bool Equals(CellKey other)
			{
				if (X == other.X)
				{
					return Z == other.Z;
				}
				return false;
			}

			public override bool Equals(object obj)
			{
				if (obj is CellKey other)
				{
					return Equals(other);
				}
				return false;
			}

			public override int GetHashCode()
			{
				return (X * 397) ^ Z;
			}
		}

		private const float CellSize = 10f;

		private static readonly object Gate = new object();

		private static readonly Dictionary<CellKey, List<Container>> Cells = new Dictionary<CellKey, List<Container>>();

		private static readonly Dictionary<int, CellKey> Membership = new Dictionary<int, CellKey>();

		internal static void Register(Container container)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)container == (Object)null)
			{
				return;
			}
			lock (Gate)
			{
				int instanceID = ((Object)container).GetInstanceID();
				CellKey cellKey = CellKey.From(((Component)container).transform.position);
				CellKey value;
				bool num = Membership.TryGetValue(instanceID, out value);
				bool flag = num && value.Equals(cellKey);
				List<Container> value2;
				bool exactContainerPresent = flag && Cells.TryGetValue(value, out value2) && ContainsExact(value2, container);
				if (!SpatialMembershipPolicy.CanRetainExistingRegistration(num, flag, exactContainerPresent))
				{
					UnregisterLocked(container, instanceID);
					if (!Cells.TryGetValue(cellKey, out var value3))
					{
						value3 = new List<Container>();
						Cells.Add(cellKey, value3);
					}
					value3.Add(container);
					Membership[instanceID] = cellKey;
				}
			}
		}

		internal static void Unregister(Container container)
		{
			if (container == null)
			{
				return;
			}
			lock (Gate)
			{
				UnregisterLocked(container, ((Object)container).GetInstanceID());
			}
		}

		internal static IReadOnlyList<Container> Query(Vector3 origin, float radius, int maximumCandidates)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//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_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			radius = Math.Max(1f, Math.Min(50f, radius));
			maximumCandidates = Math.Max(1, Math.Min(256, maximumCandidates));
			List<Candidate> list = new List<Candidate>(maximumCandidates);
			float num = radius * radius;
			CellKey cellKey = CellKey.From(origin - new Vector3(radius, 0f, radius));
			CellKey cellKey2 = CellKey.From(origin + new Vector3(radius, 0f, radius));
			lock (Gate)
			{
				for (int i = cellKey.X; i <= cellKey2.X; i++)
				{
					for (int j = cellKey.Z; j <= cellKey2.Z; j++)
					{
						CellKey key = new CellKey(i, j);
						if (!Cells.TryGetValue(key, out var value))
						{
							continue;
						}
						for (int num2 = value.Count - 1; num2 >= 0; num2--)
						{
							Container val = value[num2];
							if ((Object)(object)val == (Object)null)
							{
								value.RemoveAt(num2);
							}
							else
							{
								Vector3 val2 = ((Component)val).transform.position - origin;
								float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude;
								if (!(sqrMagnitude > num))
								{
									list.Add(new Candidate(val, sqrMagnitude));
									if (list.Count > maximumCandidates)
									{
										RemoveWorstCandidate(list);
									}
								}
							}
						}
						if (value.Count == 0)
						{
							Cells.Remove(key);
						}
					}
				}
			}
			list.Sort(CompareCandidate);
			List<Container> list2 = new List<Container>(list.Count);
			foreach (Candidate item in list)
			{
				list2.Add(item.Container);
			}
			return list2.AsReadOnly();
		}

		private static void RemoveWorstCandidate(List<Candidate> candidates)
		{
			int index = 0;
			for (int i = 1; i < candidates.Count; i++)
			{
				if (CompareCandidate(candidates[index], candidates[i]) < 0)
				{
					index = i;
				}
			}
			candidates.RemoveAt(index);
		}

		private static int CompareCandidate(Candidate left, Candidate right)
		{
			int num = left.DistanceSquared.CompareTo(right.DistanceSquared);
			if (num == 0)
			{
				return StringComparer.Ordinal.Compare(ValheimReflection.ContainerEndpointId(left.Container), ValheimReflection.ContainerEndpointId(right.Container));
			}
			return num;
		}

		internal static void Clear()
		{
			lock (Gate)
			{
				Cells.Clear();
				Membership.Clear();
			}
		}

		private static void UnregisterLocked(Container container, int instanceId)
		{
			if (!Membership.TryGetValue(instanceId, out var value))
			{
				return;
			}
			if (Cells.TryGetValue(value, out var value2))
			{
				value2.RemoveAll((Container val) => (Object)(object)val == (Object)null || val == container);
				if (value2.Count == 0)
				{
					Cells.Remove(value);
				}
			}
			Membership.Remove(instanceId);
		}

		private static bool ContainsExact(List<Container> entries, Container container)
		{
			if (entries == null)
			{
				return false;
			}
			for (int i = 0; i < entries.Count; i++)
			{
				if (entries[i] == container)
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class CraftingDiagnostics
	{
		private static readonly object Gate = new object();

		private static readonly Dictionary<string, DateTime> LastRepeatedTraceUtc = new Dictionary<string, DateTime>(StringComparer.Ordinal);

		private static readonly TimeSpan RepeatWindow = TimeSpan.FromSeconds(2.0);

		internal static void TraceAction(string operation, string reason, string detail = null)
		{
			Trace(operation, reason, detail, suppressRepeated: false);
		}

		internal static void TraceGate(string operation, string reason, string detail = null)
		{
			Trace(operation, reason, detail, suppressRepeated: true);
		}

		internal static void ResetRepeatSuppression()
		{
			lock (Gate)
			{
				LastRepeatedTraceUtc.Clear();
			}
		}

		private static void Trace(string operation, string reason, string detail, bool suppressRepeated)
		{
			if (Configuration.DetailedLogging == null || !Configuration.DetailedLogging.Value)
			{
				return;
			}
			operation = (string.IsNullOrWhiteSpace(operation) ? "operation" : operation.Trim());
			reason = (string.IsNullOrWhiteSpace(reason) ? "unspecified" : reason.Trim());
			if (suppressRepeated)
			{
				string key = operation + "\n" + reason;
				DateTime utcNow = DateTime.UtcNow;
				lock (Gate)
				{
					if (LastRepeatedTraceUtc.TryGetValue(key, out var value) && utcNow - value < RepeatWindow)
					{
						return;
					}
					if (LastRepeatedTraceUtc.Count >= 256)
					{
						LastRepeatedTraceUtc.Clear();
					}
					LastRepeatedTraceUtc[key] = utcNow;
				}
			}
			string text = (string.IsNullOrWhiteSpace(detail) ? string.Empty : ("; " + detail.Trim()));
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)("[Detailed] " + operation + " -> " + reason + text));
			}
		}
	}
	internal static class CraftingRequirementUiContext
	{
		[ThreadStatic]
		private static Recipe _currentRecipe;

		[ThreadStatic]
		private static Piece _currentPiece;

		internal static Recipe CurrentRecipe => _currentRecipe;

		internal static Piece CurrentPiece => _currentPiece;

		internal static Recipe Push(InventoryGui inventoryGui)
		{
			Recipe currentRecipe = _currentRecipe;
			_currentRecipe = ValheimReflection.SelectedRecipe(inventoryGui);
			return currentRecipe;
		}

		internal static void Restore(Recipe previous)
		{
			_currentRecipe = previous;
		}

		internal static Piece PushPiece(Piece piece)
		{
			Piece currentPiece = _currentPiece;
			_currentPiece = piece;
			return currentPiece;
		}

		internal static void RestorePiece(Piece previous)
		{
			_currentPiece = previous;
		}
	}
	internal static class CraftingRuntime
	{
		private sealed class ActiveOperation
		{
			internal Player Player { get; }

			internal Requirement[] Requirements { get; }

			internal int Quality { get; }

			internal int ItemQuality { get; }

			internal int Multiplier { get; }

			internal Recipe Recipe { get; }

			internal Piece Piece { get; }

			internal MaterialConsumptionLease Lease { get; }

			internal bool VanillaConsumeReached { get; set; }

			internal bool OutputCreated { get; set; }

			internal GameObject PlacedObject { get; set; }

			internal ActiveOperation(Player player, Requirement[] requirements, int quality, int itemQuality, int multiplier, Recipe recipe, Piece piece, MaterialConsumptionLease lease)
			{
				Player = player;
				Requirements = requirements;
				Quality = quality;
				ItemQuality = itemQuality;
				Multiplier = multiplier;
				Recipe = recipe;
				Piece = piece;
				Lease = lease;
			}
		}

		private sealed class PendingCraft
		{
			internal Player Player { get; }

			internal Recipe Recipe { get; }

			internal CraftingStation Station { get; }

			internal Vector3 Origin { get; }

			internal float Range { get; }

			internal IReadOnlyList<MaterialRequirement> Requirements { get; }

			internal int Quality { get; }

			internal int Multiplier { get; }

			internal PendingCraft(Player player, Recipe recipe, CraftingStation station, Vector3 origin, float range, IReadOnlyList<MaterialRequirement> requirements, int quality, int multiplier)
			{
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				Player = player;
				Recipe = recipe;
				Station = station;
				Origin = origin;
				Range = range;
				Requirements = requirements;
				Quality = quality;
				Multiplier = multiplier;
			}
		}

		private readonly struct BreakdownKey : IEquatable<BreakdownKey>
		{
			private long PlayerId { get; }

			private string StationId { get; }

			private string ResourceId { get; }

			private int Required { get; }

			internal BreakdownKey(long playerId, string stationId, string resourceId, int required)
			{
				PlayerId = playerId;
				StationId = stationId;
				ResourceId = resourceId;
				Required = required;
			}

			public bool Equals(BreakdownKey other)
			{
				if (PlayerId == other.PlayerId && Required == other.Required && string.Equals(StationId, other.StationId, StringComparison.Ordinal))
				{
					return string.Equals(ResourceId, other.ResourceId, StringComparison.Ordinal);
				}
				return false;
			}

			public override bool Equals(object obj)
			{
				if (obj is BreakdownKey other)
				{
					return Equals(other);
				}
				return false;
			}

			public override int GetHashCode()
			{
				return (((((PlayerId.GetHashCode() * 397) ^ ((StationId != null) ? StringComparer.Ordinal.GetHashCode(StationId) : 0)) * 397) ^ ((ResourceId != null) ? StringComparer.Ordinal.GetHashCode(ResourceId) : 0)) * 397) ^ Required;
			}
		}

		private readonly struct BreakdownEntry
		{
			internal int Nearby { get; }

			internal float ExpiresAt { get; }

			internal BreakdownEntry(int nearby, float expiresAt)
			{
				Nearby = nearby;
				ExpiresAt = expiresAt;
			}
		}

		private static readonly ExactMaterialPlanner Planner = new ExactMaterialPlanner();

		private static readonly ExactMaterialTransactionEngine Engine = new ExactMaterialTransactionEngine(Planner);

		private static ManualLogSource _log;

		private static WorkshopAccessRuntime _workshopAccess;

		private static ContainerQueryRuntime _containerQuery;

		private static readonly object AvailabilityCacheGate = new object();

		private static readonly Dictionary<BreakdownKey, BreakdownEntry> AvailabilityCache = new Dictionary<BreakdownKey, BreakdownEntry>();

		private static string _lastStationStatusKey;

		private static float _lastStationStatusAt = -100f;

		[ThreadStatic]
		private static ActiveOperation _activeOperation;

		[ThreadStatic]
		private static PendingCraft _pendingCraft;

		[ThreadStatic]
		private static bool _insidePlacementUpdate;

		internal static bool IsInitialized => _containerQuery != null;

		internal static void Initialize(ManualLogSource log)
		{
			_log = log ?? throw new ArgumentNullException("log");
			_workshopAccess = new WorkshopAccessRuntime();
			_containerQuery = new ContainerQueryRuntime(_workshopAccess);
		}

		internal static void Shutdown()
		{
			_pendingCraft = null;
			_insidePlacementUpdate = false;
			CleanupActive(success: false);
			ContainerSpatialIndex.Clear();
			_containerQuery = null;
			_workshopAccess = null;
			_log = null;
			lock (AvailabilityCacheGate)
			{
				AvailabilityCache.Clear();
			}
			_lastStationStatusKey = null;
			_lastStationStatusAt = -100f;
		}

		internal static void OnConfigurationChanged()
		{
			lock (AvailabilityCacheGate)
			{
				AvailabilityCache.Clear();
			}
			_lastStationStatusKey = null;
			_lastStationStatusAt = -100f;
		}

		internal static bool CanUseStation(CraftingStation station, Player player, out string reason)
		{
			reason = "vanilla";
			if (!Configuration.Enabled.Value)
			{
				CraftingDiagnostics.TraceGate("station-access", "bypass:mod-disabled");
				return true;
			}
			if (!IsInitialized)
			{
				CraftingDiagnostics.TraceGate("station-access", "bypass:runtime-unavailable");
				return true;
			}
			if (!ValheimReflection.CanMutateLocalPlayer(player))
			{
				CraftingDiagnostics.TraceGate("station-access", "bypass:local-player-owner-required");
				return true;
			}
			WorkshopAccessDecision workshopAccessDecision = _workshopAccess.Evaluate(station, player, WorkshopAction.StationUse);
			reason = workshopAccessDecision.ReasonCode;
			CraftingDiagnostics.TraceGate("station-access", workshopAccessDecision.Allowed ? "allowed" : ("denied:" + reason));
			return workshopAccessDecision.Allowed;
		}

		internal static void ShowStationStatus(CraftingStation station, Player player)
		{
			if (Configuration.ShowStatusMessages.Value && !((Object)(object)player == (Object)null))
			{
				NearbyCraftingFeatureState nearbyCraftingFeatureState = EvaluateNearbyCraftingState(player, null, station, evaluateRecipeRules: false);
				string text = (((Object)(object)station == (Object)null) ? "none" : ValheimReflection.StationEndpointId(station)) + "|" + nearbyCraftingFeatureState.ReasonCode;
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (!string.Equals(_lastStationStatusKey, text, StringComparison.Ordinal) || !(realtimeSinceStartup - _lastStationStatusAt < 2f))
				{
					_lastStationStatusKey = text;
					_lastStationStatusAt = realtimeSinceStartup;
					string text2 = (nearbyCraftingFeatureState.IsReady ? ("Runic Crafting: nearby materials ON (up to " + GetStationRange(station).ToString("0.#", CultureInfo.InvariantCulture) + "m); recipe rows show combined/required totals with C/N/T/M hover details") : ExplainNearbyState(nearbyCraftingFeatureState));
					((Character)player).Message((MessageType)1, text2, 0, (Sprite)null);
					CraftingDiagnostics.TraceAction("station-status", nearbyCraftingFeatureState.ReasonCode, text2);
				}
			}
		}

		internal static bool BeforeCraft(InventoryGui gui, Player player, Recipe recipe, ItemData upgradeItem, bool multiCrafting, int multiCraftAmount)
		{
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			CraftingDiagnostics.TraceAction("craft-attempt", "received");
			CraftingStation val = (((Object)(object)player != (Object)null) ? player.GetCurrentCraftingStation() : null);
			if (Configuration.Enabled.Value && IsInitialized && ValheimReflection.CanMutateLocalPlayer(player) && (Object)(object)val != (Object)null && !CanUseStation(val, player, out var reason))
			{
				((Character)player).Message((MessageType)2, "Station use denied: " + reason, 0, (Sprite)null);
				CraftingDiagnostics.TraceAction("craft-cancelled", "station-use-denied:" + reason);
				return false;
			}
			NearbyCraftingFeatureState nearbyCraftingFeatureState = EvaluateNearbyCraftingState(player, recipe, val, evaluateRecipeRules: true);
			if (!nearbyCraftingFeatureState.IsReady)
			{
				CraftingDiagnostics.TraceAction("nearby-craft-bypass", nearbyCraftingFeatureState.ReasonCode, "vanilla handles the action");
				return true;
			}
			int num = ((upgradeItem == null) ? 1 : (upgradeItem.m_quality + 1));
			if (num > recipe.m_item.m_itemData.m_shared.m_maxQuality || (upgradeItem != null && !((Humanoid)player).GetInventory().ContainsItem(upgradeItem)))
			{
				CraftingDiagnostics.TraceAction("nearby-craft-bypass", "invalid-upgrade-state", "vanilla handles the action");
				return true;
			}
			int multiplier = ((!multiCrafting) ? 1 : Math.Max(1, multiCraftAmount));
			if (!TryBuildRequirements(recipe.m_resources, num, multiplier, out var requirements))
			{
				CraftingDiagnostics.TraceAction("nearby-craft-bypass", "no-valid-material-requirements", "vanilla handles the action");
				return true;
			}
			if (InventorySatisfies(((Humanoid)player).GetInventory(), requirements))
			{
				CraftingDiagnostics.TraceAction("nearby-craft-bypass", "carried-inventory-satisfies-cost", "vanilla consumes carried materials");
				return true;
			}
			if ((Object)(object)val == (Object)null)
			{
				CraftingDiagnostics.TraceAction("nearby-craft-bypass", "crafting-station-required", "vanilla handles the action");
				return true;
			}
			float stationRange = GetStationRange(val);
			Vector3 origin = (((Object)(object)val != (Object)null) ? ((Component)val).transform.position : ((Component)player).transform.position);
			if (upgradeItem == null)
			{
				if (_pendingCraft != null || _activeOperation != null)
				{
					((Character)player).Message((MessageType)2, "Nearby crafting cancelled: another material operation is active", 0, (Sprite)null);
					CraftingDiagnostics.TraceAction("craft-cancelled", "another-material-operation-is-active");
					return false;
				}
				if (!CheckAvailability(player, val, origin, stationRange, requirements, "runic.crafting.recipe-preview", stationlessAccessAuthorized: false, out var plan, out var reason2))
				{
					CraftingDiagnostics.TraceAction("nearby-craft-bypass", reason2, "vanilla reports the material shortage");
					return true;
				}
				if (!plan.Lines.Any((MaterialPlanLine line) => !line.SourceId.StartsWith("valheim.player:", StringComparison.Ordinal)))
				{
					CraftingDiagnostics.TraceAction("nearby-craft-bypass", "no-nearby-source-needed", "vanilla consumes carried materials");
					return true;
				}
				_pendingCraft = new PendingCraft(player, recipe, val, origin, stationRange, requirements, num, multiplier);
				CraftingDiagnostics.TraceAction("nearby-craft", "pending-vanilla-capacity-check");
				return true;
			}
			if (!TryBeginNearbyOperation(player, val, origin, stationRange, requirements, "runic.crafting.recipe", stationlessAccessAuthorized: false, recipe.m_resources, num, -1, multiplier, recipe, null, out var reason3))
			{
				((Character)player).Message((MessageType)2, "Nearby crafting cancelled: " + reason3, 0, (Sprite)null);
				CraftingDiagnostics.TraceAction("craft-cancelled", reason3);
				return false;
			}
			CraftingDiagnostics.TraceAction("nearby-craft", "exact-material-operation-started");
			return true;
		}

		internal static bool AfterCraftCapacityCheck(Inventory inventory, GameObject prefab, bool vanillaAllowed)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			PendingCraft pendingCraft = _pendingCraft;
			if (pendingCraft == null || inventory == null || (Object)(object)prefab == (Object)null || inventory != ((Humanoid)pendingCraft.Player).GetInventory() || (Object)(object)prefab != (Object)(object)((Component)pendingCraft.Recipe.m_item).gameObject)
			{
				return vanillaAllowed;
			}
			_pendingCraft = null;
			if (!vanillaAllowed)
			{
				CraftingDiagnostics.TraceAction("craft-cancelled", "vanilla-output-capacity-check-failed");
				return false;
			}
			if (TryBeginNearbyOperation(pendingCraft.Player, pendingCraft.Station, pendingCraft.Origin, pendingCraft.Range, pendingCraft.Requirements, "runic.crafting.recipe", stationlessAccessAuthorized: false, pendingCraft.Recipe.m_resources, pendingCraft.Quality, -1, pendingCraft.Multiplier, pendingCraft.Recipe, null, out var reason))
			{
				CraftingDiagnostics.TraceAction("nearby-craft", "exact-material-operation-started");
				return true;
			}
			((Character)pendingCraft.Player).Message((MessageType)2, "Nearby crafting cancelled: " + reason, 0, (Sprite)null);
			CraftingDiagnostics.TraceAction("craft-cancelled", reason);
			return false;
		}

		internal static void CraftOutputAdded(Inventory inventory, ItemData result)
		{
			ActiveOperation activeOperation = _activeOperation;
			if (activeOperation != null && !((Object)(object)activeOperation.Recipe == (Object)null) && result != null && inventory == ((Humanoid)activeOperation.Player).GetInventory())
			{
				activeOperation.OutputCreated = true;
			}
		}

		internal static void CancelPendingCraft()
		{
			_pendingCraft = null;
			CraftingDiagnostics.TraceAction("craft-cancelled", "pending-capacity-hook-failed");
		}

		internal static void FinishCraft(Exception exception)
		{
			_pendingCraft = null;
			if (exception != null)
			{
				CraftingDiagnostics.TraceAction("craft-finished", "exception:" + exception.GetType().Name);
			}
			CleanupActive(_activeOperation != null && (_activeOperation.VanillaConsumeReached || _activeOperation.OutputCreated));
		}

		internal static void BeginPlacementScope()
		{
			_insidePlacementUpdate = true;
		}

		internal static void FinishPlacementScope(Exception exception)
		{
			try
			{
				if (_activeOperation != null)
				{
					PlacementLeaseDisposition placementLeaseDisposition = PlacementLeasePolicy.Resolve(_activeOperation.OutputCreated || (Object)(object)_activeOperation.PlacedObject != (Object)null, _activeOperation.VanillaConsumeReached);
					CraftingDiagnostics.TraceAction("nearby-build-finalize", (placementLeaseDisposition == PlacementLeaseDisposition.Commit) ? "commit" : "rollback", (exception == null) ? "placement-update-completed" : ("placement-update-exception:" + exception.GetType().Name));
					CleanupActive(placementLeaseDisposition == PlacementLeaseDisposition.Commit);
				}
			}
			finally
			{
				_insidePlacementUpdate = false;
			}
		}

		internal static bool BeforePlacePiece(Player player, Piece piece, out string reason)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			reason = "vanilla";
			if (!_insidePlacementUpdate)
			{
				CraftingDiagnostics.TraceAction("nearby-build-bypass", "outside-vanilla-placement-scope");
				return true;
			}
			if ((Object)(object)player == (Object)null || (Object)(object)piece == (Object)null)
			{
				CraftingDiagnostics.TraceAction("nearby-build-bypass", "invalid-player-or-piece");
				return true;
			}
			CraftingStation val = (((Object)(object)piece.m_craftingStation == (Object)null) ? null : CraftingStation.HaveBuildStationInRange(piece.m_craftingStation.m_name, ((Component)player).transform.position));
			if (Configuration.Enabled.Value && IsInitialized && ValheimReflection.CanMutateLocalPlayer(player) && (Object)(object)val != (Object)null && !CanUseStation(val, player, out reason))
			{
				reason = "station-use:" + reason;
				CraftingDiagnostics.TraceAction("build-cancelled", reason);
				return false;
			}
			if (!CanUseNearbyBuilding(player, piece, out var reason2))
			{
				CraftingDiagnostics.TraceAction("nearby-build-bypass", reason2, "vanilla handles the placement");
				return true;
			}
			if (!TryBuildRequirements(piece.m_resources, 0, 1, out var requirements))
			{
				CraftingDiagnostics.TraceAction("nearby-build-bypass", "no-valid-material-requirements", "vanilla handles the placement");
				return true;
			}
			if (InventorySatisfies(((Humanoid)player).GetInventory(), requirements))
			{
				CraftingDiagnostics.TraceAction("nearby-build-bypass", "carried-inventory-satisfies-cost", "vanilla consumes carried materials");
				return true;
			}
			BuildMaterialQueryScope buildMaterialQueryScope = BuildMaterialQueryScopePolicy.Resolve((Object)(object)piece.m_craftingStation != (Object)null, (Object)(object)val != (Object)null);
			if (buildMaterialQueryScope == BuildMaterialQueryScope.RequiredStationUnavailable)
			{
				reason = "required-station-unavailable";
				CraftingDiagnostics.TraceAction("build-cancelled", reason);
				return false;
			}
			bool flag = buildMaterialQueryScope == BuildMaterialQueryScope.PlayerLocalStationless;
			float range = (flag ? Configuration.SafeStationlessBuildRange : GetStationRange(val));
			Vector3 origin = (flag ? ((Component)player).transform.position : ((Component)val).transform.position);
			if (!TryBeginNearbyOperation(player, val, origin, range, requirements, "runic.crafting.build", flag, piece.m_resources, 0, -1, 1, null, piece, out reason))
			{
				CraftingDiagnostics.TraceAction("build-cancelled", reason);
				return false;
			}
			CraftingDiagnostics.TraceAction("nearby-build", "exact-material-operation-started");
			return true;
		}

		internal static void PlacePieceReturned(bool succeeded)
		{
			if (_activeOperation != null)
			{
				if (succeeded)
				{
					_activeOperation.OutputCreated = true;
					CraftingDiagnostics.TraceAction("nearby-build", "vanilla-piece-created");
				}
				else
				{
					CraftingDiagnostics.TraceAction("nearby-build", "vanilla-placement-returned-false; rolling-back");
					CleanupActive(success: false);
				}
			}
		}

		internal static void PlacementObjectInstantiated(GameObject placedObject, Player player, Piece piece)
		{
			ActiveOperation activeOperation = _activeOperation;
			if (_insidePlacementUpdate && activeOperation != null && !((Object)(object)placedObject == (Object)null) && activeOperation.Player == player && activeOperation.Piece == piece)
			{
				activeOperation.PlacedObject = placedObject;
			}
		}

		internal static bool BeforeHaveRecipeRequirements(Player player, Recipe recipe, bool discover, int quality, int multiplier, ref bool result)
		{
			if (_activeOperation != null && (Object)(object)_activeOperation.Player == (Object)(object)player && _activeOperation.Recipe == recipe && !discover && _activeOperation.Quality == quality && _activeOperation.Multiplier == multiplier)
			{
				result = true;
				return false;
			}
			return true;
		}

		internal static void AddNearbyRecipeAvailability(Player player, Recipe recipe, bool discover, int quality, int multiplier, ref bool result)
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			if (result)
			{
				CraftingDiagnostics.TraceGate("recipe-preview", "vanilla-requirements-satisfied");
				return;
			}
			if (discover)
			{
				CraftingDiagnostics.TraceGate("recipe-preview", "discovery-check-remains-vanilla");
				return;
			}
			CraftingStation val = (((Object)(object)player != (Object)null) ? player.GetCurrentCraftingStation() : null);
			NearbyCraftingFeatureState nearbyCraftingFeatureState = EvaluateNearbyCraftingState(player, recipe, val, evaluateRecipeRules: true);
			List<MaterialRequirement> requirements;
			if (!nearbyCraftingFeatureState.IsReady)
			{
				CraftingDiagnostics.TraceGate("recipe-preview", nearbyCraftingFeatureState.ReasonCode);
			}
			else if (TryBuildRequirements(recipe.m_resources, quality, multiplier, out requirements))
			{
				result = CheckAvailability(player, val, ((Object)(object)val != (Object)null) ? ((Component)val).transform.position : ((Component)player).transform.position, GetStationRange(val), requirements, "runic.crafting.recipe-preview", stationlessAccessAuthorized: false, out var _, out var reason);
				CraftingDiagnostics.TraceGate("recipe-preview", result ? "nearby-materials-satisfy-cost" : reason);
			}
		}

		internal static void AddNearbyPieceAvailability(Player player, Piece piece, RequirementMode mode, ref bool result)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			if (result || (int)mode != 0)
			{
				return;
			}
			CraftingStation station;
			NearbyCraftingFeatureState nearbyCraftingFeatureState = EvaluateNearbyBuildingState(player, piece, out station);
			List<MaterialRequirement> requirements;
			if (!nearbyCraftingFeatureState.IsReady)
			{
				CraftingDiagnostics.TraceGate("build-preview", nearbyCraftingFeatureState.ReasonCode);
			}
			else if (TryBuildRequirements(piece.m_resources, 0, 1, out requirements))
			{
				BuildMaterialQueryScope buildMaterialQueryScope = BuildMaterialQueryScopePolicy.Resolve((Object)(object)piece.m_craftingStation != (Object)null, (Object)(object)station != (Object)null);
				if (buildMaterialQueryScope != BuildMaterialQueryScope.RequiredStationUnavailable)
				{
					bool flag = buildMaterialQueryScope == BuildMaterialQueryScope.PlayerLocalStationless;
					Vector3 origin = (flag ? ((Component)player).transform.position : ((Component)station).transform.position);
					float range = (flag ? Configuration.SafeStationlessBuildRange : GetStationRange(station));
					result = CheckAvailability(player, station, origin, range, requirements, "runic.crafting.build-preview", flag, out var _, out var reason);
					CraftingDiagnostics.TraceGate("build-preview", result ? "nearby-materials-satisfy-cost" : reason);
				}
			}
		}

		internal static bool BeforeVanillaConsume(Player player, Requirement[] requirements, int quality, int itemQuality, int multiplier)
		{
			ActiveOperation activeOperation = _activeOperation;
			if (activeOperation == null || (Object)(object)activeOperation.Player != (Object)(object)player || activeOperation.Requirements != requirements || activeOperation.Quality != quality || activeOperation.ItemQuality != itemQuality || activeOperation.Multiplier != multiplier)
			{
				return true;
			}
			activeOperation.VanillaConsumeReached = true;
			return false;
		}

		internal static bool TryGetAvailabilityBreakdown(Player player, Recipe recipe, Requirement requirement, int quality, int multiplier, out int carried, out int nearby, out int required, out NearbyCraftingFeatureState state)
		{
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			carried = (nearby = (required = 0));
			state = new NearbyCraftingFeatureState(isReady: false, "requirement-unavailable", "N:OFF(requirement)");
			if (requirement == null || (Object)(object)requirement.m_resItem == (Object)null || (Object)(object)player == (Object)null)
			{
				return false;
			}
			required = checked(requirement.GetAmount(quality) * multiplier);
			if (required <= 0)
			{
				return false;
			}
			string text = ValheimReflection.ResourceId(requirement.m_resItem);
			if (text.Length == 0)
			{
				return false;
			}
			carried = ValheimReflection.CountRequirementItems(((Humanoid)player).GetInventory(), text);
			CraftingStation currentCraftingStation = player.GetCurrentCraftingStation();
			state = EvaluateNearbyCraftingState(player, recipe, currentCraftingStation, evaluateRecipeRules: true);
			if (!state.IsReady)
			{
				CraftingDiagnostics.TraceGate("requirement-row", state.ReasonCode);
				return true;
			}
			return TryResolveNearbyBreakdown(player, currentCraftingStation, text, required, carried, "requirement-row", "runic.crafting.ui", ((Component)currentCraftingStation).transform.position, GetStationRange(currentCraftingStation), stationlessAccessAuthorized: false, ref nearby, ref state);
		}

		internal static bool TryGetBuildAvailabilityBreakdown(Player player, Piece piece, Requirement requirement, out int carried, out int nearby, out int required, out NearbyCraftingFeatureState state)
		{
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			carried = (nearby = (required = 0));
			state = new NearbyCraftingFeatureState(isReady: false, "requirement-unavailable", "N:OFF(requirement)");
			if (requirement == null || (Object)(object)requirement.m_resItem == (Object)null || (Object)(object)player == (Object)null || (Object)(object)piece == (Object)null)
			{
				return false;
			}
			required = requirement.GetAmount(0);
			if (required <= 0)
			{
				return false;
			}
			string text = ValheimReflection.ResourceId(requirement.m_resItem);
			if (text.Length == 0)
			{
				return false;
			}
			carried = ValheimReflection.CountRequirementItems(((Humanoid)player).GetInventory(), text);
			state = EvaluateNearbyBuildingState(player, piece, out var station);
			if (!state.IsReady)
			{
				CraftingDiagnostics.TraceGate("build-requirement-row", state.ReasonCode);
				return true;
			}
			BuildMaterialQueryScope buildMaterialQueryScope = BuildMaterialQueryScopePolicy.Resolve((Object)(object)piece.m_craftingStation != (Object)null, (Object)(object)station != (Object)null);
			if (buildMaterialQueryScope == BuildMaterialQueryScope.RequiredStationUnavailable)
			{
				state = BuildingFailureState("required-station-unavailable");
				return true;
			}
			bool flag = buildMaterialQueryScope == BuildMaterialQueryScope.PlayerLocalStationless;
			Vector3 origin = (flag ? ((Component)player).transform.position : ((Component)station).transform.position);
			float range = (flag ? Configuration.SafeStationlessBuildRange : GetStationRange(station));
			return TryResolveNearbyBreakdown(player, station, text, required, carried, "build-requirement-row", "runic.crafting.build-ui", origin, range, flag, ref nearby, ref state);
		}

		private static bool TryResolveNearbyBreakdown(Player player, CraftingStation station, string resource, int required, int carried, string diagnosticOperation, string queryPurpose, Vector3 origin, float range, bool stationlessAccessAuthorized, ref int nearby, ref NearbyCraftingFeatureState state)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			BreakdownKey key = new BreakdownKey(player.GetPlayerID(), stationlessAccessAuthorized ? StationlessAnchorId(origin) : ValheimReflection.StationEndpointId(station), resource, required);
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			lock (AvailabilityCacheGate)
			{
				if (AvailabilityCache.TryGetValue(key, out var value) && value.ExpiresAt >= realtimeSinceStartup)
				{
					nearby = value.Nearby;
					CraftingDiagnostics.TraceGate(diagnosticOperation, "ready", "resource=" + resource + "; required=" + required + "; carried=" + carried + "; nearby=" + nearby + "; cached=true");
					return true;
				}
			}
			MaterialRequirement[] requirements = new MaterialRequirement[1]
			{
				new MaterialRequirement(resource, required)
			};
			string reasonCode;
			IReadOnlyList<IMutableMaterialSource> readOnlyList = _containerQuery.ResolveSources(player, station, origin, range, requirements, queryPurpose, stationlessAccessAuthorized, out reasonCode);
			if (IsBlockingQueryReason(reasonCode))
			{
				state = QueryFailureState(reasonCode);
				CraftingDiagnostics.TraceGate(diagnosticOperation, state.ReasonCode);
				return true;
			}
			foreach (IMutableMaterialSource item in readOnlyList)
			{
				MaterialSourceSnapshot materialSourceSnapshot = item.Snapshot();
				if (materialSourceSnapshot.Kind == MaterialSourceKind.NearbyContainer)
				{
					int num = materialSourceSnapshot.Available(resource);
					nearby = ((nearby > int.MaxValue - num) ? int.MaxValue : (nearby + num));
				}
			}
			lock (AvailabilityCacheGate)
			{
				if (AvailabilityCache.Count >= 512)
				{
					AvailabilityCache.Clear();
				}
				AvailabilityCache[key] = new BreakdownEntry(nearby, realtimeSinceStartup + 0.25f);
			}
			CraftingDiagnostics.TraceGate(diagnosticOperation, "ready", "resource=" + resource + "; required=" + required + "; carried=" + carried + "; nearby=" + nearby + "; source-query=" + reasonCode);
			return true;
		}

		private static bool TryBeginNearbyOperation(Player player, CraftingStation station, Vector3 origin, float range, IReadOnlyList<MaterialRequirement> requirements, string purpose, bool stationlessAccessAuthorized, Requirement[] vanillaRequirements, int quality, int itemQuality, int multiplier, Recipe recipe, Piece piece, out string reason)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			reason = "insufficient-materials";
			if (_activeOperation != null)
			{
				reason = "another-material-operation-is-active";
				CraftingDiagnostics.TraceAction(purpose, reason);
				return false;
			}
			string reasonCode;
			IReadOnlyList<IMutableMaterialSource> readOnlyList = _containerQuery.ResolveSources(player, station, origin, range, requirements, purpose, stationlessAccessAuthorized, out reasonCode);
			CraftingDiagnostics.TraceAction(purpose + ":source-query", reasonCode, "sources=" + readOnlyList.Count + "; range=" + range.ToString("0.#", CultureInfo.InvariantCulture) + "m");
			MaterialSourceSnapshot[] sources = readOnlyList.Select((IMutableMaterialSource source) => source.Snapshot()).ToArray();
			if (Configuration.DetailedLogging.Value)
			{
				CraftingDiagnostics.TraceAction(purpose + ":availability", "evaluated", FormatMaterialAvailability(requirements, sources));
			}
			if (!Planner.TryPlan(requirements, sources, out var plan, out var denialReason))
			{
				if (reasonCode.StartsWith("station-use:", StringComparison.Ordinal) || reasonCode.StartsWith("local-materials:", StringComparison.Ordinal))
				{
					reason = reasonCode;
				}
				else
				{
					reason = denialReason;
				}
				CraftingDiagnostics.TraceAction(purpose, reason);
				return false;
			}
			if (!plan.Lines.Any((MaterialPlanLine line) => !line.SourceId.StartsWith("valheim.player:", StringComparison.Ordinal)))
			{
				reason = "no-nearby-source-needed";
				CraftingDiagnostics.TraceAction(purpose, reason);
				return false;
			}
			if (!Engine.TryBegin(requirements, readOnlyList, out var lease, out var denialReason2))
			{
				reason = denialReason2;
				CraftingDiagnostics.TraceAction(purpose, reason);
				return false;
			}
			_activeOperation = new ActiveOperation(player, vanillaRequirements, quality, itemQuality, multiplier, recipe, piece, lease);
			if (Configuration.DetailedLogging.Value)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)$"Reserved exact {purpose} plan with {lease.Plan.Lines.Count} source lines ({reasonCode}).");
				}
			}
			return true;
		}

		private static bool CheckAvailability(Player player, CraftingStation station, Vector3 origin, float range, IReadOnlyList<MaterialRequirement> requirements, string purpose, bool stationlessAccessAuthorized, out MaterialPlan plan, out string reason)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			plan = null;
			reason = "insufficient-materials";
			if ((Object)(object)station == (Object)null && !stationlessAccessAuthorized)
			{
				reason = "crafting-station-required";
				return false;
			}
			if ((Object)(object)station != (Object)null && !CanUseStation(station, player, out var reason2))
			{
				reason = "station-use:" + reason2;
				return false;
			}
			string reasonCode;
			IReadOnlyList<IMutableMaterialSource> source = _containerQuery.ResolveSources(player, station, origin, range, requirements, purpose, stationlessAccessAuthorized, out reasonCode);
			if (IsBlockingQueryReason(reasonCode))
			{
				reason = reasonCode;
				return false;
			}
			string denialReason;
			bool flag = Planner.TryPlan(requirements, source.Select((IMutableMaterialSource mutableMaterialSource) => mutableMaterialSource.Snapshot()), out plan, out denialReason);
			reason = (flag ? ("ok:" + reasonCode) : denialReason);
			return flag;
		}

		private static NearbyCraftingFeatureState EvaluateNearbyCraftingState(Player player, Recipe recipe, CraftingStation station, bool evaluateRecipeRules)
		{
			bool noCostMode = ((Object)(object)player != (Object)null && player.NoCostCheat()) || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)20));
			NearbyCraftingFeatureState nearbyCraftingFeatureState = NearbyCraftingFeatureStateEvaluator.Evaluate(Configuration.Enabled.Value, Configuration.CraftFromContainers.Value, IsInitialized, ValheimReflection.CanMutateLocalPlayer(player), !evaluateRecipeRules || (Object)(object)recipe != (Object)null, evaluateRecipeRules && (Object)(object)recipe != (Object)null && recipe.m_requireOnlyOneIngredient, noCostMode, (Object)(object)station != (Object)null, stationUseAllowed: true, null, localMaterialsAllowed: true, null);
			if (!nearbyCraftingFeatureState.IsReady)
			{
				return nearbyCraftingFeatureState;
			}
			WorkshopAccessDecision workshopAccessDecision = _workshopAccess.Evaluate(station, player, WorkshopAction.StationUse);
			WorkshopAccessDecision workshopAccessDecision2 = (workshopAccessDecision.Allowed ? _workshopAccess.Evaluate(station, player, WorkshopAction.LocalMaterialUse) : new WorkshopAccessDecision(allowed: false, WorkshopAction.LocalMaterialUse, "station-use-denied"));
			return NearbyCraftingFeatureStateEvaluator.Evaluate(masterEnabled: true, craftFromContainersEnabled: true, runtimeAvailable: true, localPlayerOwner: true, recipeAvailable: true, specialOneIngredientRecipe: false, noCostMode: false, stationPresent: true, workshopAccessDecision.Allowed, workshopAccessDecision.ReasonCode, workshopAccessDecision2.Allowed, workshopAccessDecision2.ReasonCode);
		}

		private static NearbyCraftingFeatureState EvaluateNearbyBuildingState(Player player, Piece piece, out CraftingStation station)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			station = null;
			if (!CanUseNearbyBuilding(player, piece, out var reason))
			{
				return BuildingFailureState(reason);
			}
			if (!ValheimReflection.DlcAllows(piece.m_dlc))
			{
				return BuildingFailureState("dlc-entitlement-required");
			}
			if ((Object)(object)piece.m_craftingStation == (Object)null)
			{
				return new NearbyCraftingFeatureState(isReady: true, "ready-stationless", "N:ON");
			}
			station = CraftingStation.HaveBuildStationInRange(piece.m_craftingStation.m_name, ((Component)player).transform.position);
			if ((Object)(object)station == (Object)null)
			{
				return BuildingFailureState("required-station-unavailable");
			}
			WorkshopAccessDecision workshopAccessDecision = _workshopAccess.Evaluate(station, player, WorkshopAction.StationUse);
			if (!workshopAccessDecision.Allowed)
			{
				return new NearbyCraftingFeatureState(isReady: false, "station-use-denied:" + workshopAccessDecision.ReasonCode, "N:OFF(access)");
			}
			WorkshopAccessDecision workshopAccessDecision2 = _workshopAccess.Evaluate(station, player, WorkshopAction.LocalMaterialUse);
			if (!workshopAccessDecision2.Allowed)
			{
				return new NearbyCraftingFeatureState(isReady: false, "local-material-use-denied:" + workshopAccessDecision2.ReasonCode, "N:OFF(access)");
			}
			return new NearbyCraftingFeatureState(isReady: true, "ready", "N:ON");
		}

		private static NearbyCraftingFeatureState BuildingFailureState(string reason)
		{
			return new NearbyCraftingFeatureState(isReady: false, reason, reason switch
			{
				"mod-disabled" => "N:OFF(mod)", 
				"build-from-containers-disabled" => "N:OFF(config)", 
				"runtime-unavailable" => "N:OFF(startup)", 
				"local-player-owner-required" => "N:OFF(owner)", 
				"no-cost-mode" => "N:OFF(no-cost)", 
				"required-station-unavailable" => "N:OFF(station)", 
				"dlc-entitlement-required" => "N:OFF(dlc)", 
				"stationless-building-disabled" => "N:OFF(config)", 
				_ => "N:OFF(build)", 
			});
		}

		private static bool CanUseNearbyBuilding(Player player, Piece piece, out string reason)
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			reason = "ready";
			if (!Configuration.Enabled.Value)
			{
				reason = "mod-disabled";
			}
			else if (!Configuration.BuildFromContainers.Value)
			{
				reason = "build-from-containers-disabled";
			}
			else if (!IsInitialized)
			{
				reason = "runtime-unavailable";
			}
			else if (!ValheimReflection.CanMutateLocalPlayer(player))
			{
				reason = "local-player-owner-required";
			}
			else if ((Object)(object)piece == (Object)null)
			{
				reason = "piece-unavailable";
			}
			else if (!ValheimReflection.DlcAllows(piece.m_dlc))
			{
				reason = "dlc-entitlement-required";
			}
			else if (player.NoCostCheat() || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey())))
			{
				reason = "no-cost-mode";
			}
			else if ((Object)(object)piece.m_craftingStation == (Object)null)
			{
				if (!Configuration.StationlessBuildFromContainers.Value)
				{
					reason = "stationless-building-disabled";
				}
				else
				{
					StationlessPieceDecision stationlessPieceDecision = Configuration.StationlessPolicy.Evaluate(ValheimReflection.PiecePrefabId(piece));
					if (!stationlessPieceDecision.Allowed)
					{
						reason = stationlessPieceDecision.ReasonCode;
					}
				}
			}
			return reason == "ready";
		}

		private static bool IsBlockingQueryReason(string reason)
		{
			if (!string.Equals(reason, "local-player-owner-required", StringComparison.Ordinal) && !string.Equals(reason, "station-required-for-nearby-materials", StringComparison.Ordinal))
			{
				if (reason != null)
				{
					if (!reason.StartsWith("station-use:", StringComparison.Ordinal))
					{
						return reason.StartsWith("local-materials:", StringComparison.Ordinal);
					}
					return true;
				}
				return false;
			}
			return true;
		}

		private static NearbyCraftingFeatureState QueryFailureState(string reason)
		{
			string displayLabel = "N:OFF(access)";
			return new NearbyCraftingFeatureState(isReady: false, reason, displayLabel);
		}

		private static string ExplainNearbyState(NearbyCraftingFeatureState state)
		{
			string reasonCode = state.ReasonCode;
			switch (reasonCode)
			{
			case "mod-disabled":
				return "Runic Crafting: gameplay disabled in configuration";
			case "craft-from-containers-disabled":
				return "Runic Crafting: nearby materials OFF (CraftFromContainers=false)";
			case "runtime-unavailable":
				return "Runic Crafting: nearby materials unavailable; check the BepInEx log";
			case "local-player-owner-required":
				return "Runic Crafting: nearby materials require the local player owner";
			case "special-one-ingredient-recipe":
				return "Runic Crafting: this special recipe uses vanilla carried materials";
			case "no-cost-mode":
				return "Runic Crafting: no-cost mode is active; nearby containers are intentionally unused";
			case "crafting-station-required":
				return "Runic Crafting: an active crafting station is required for nearby materials";
			default:
				if (reasonCode.StartsWith("station-use-denied:", StringComparison.Ordinal))
				{
					return "Runic Crafting: nearby materials OFF because station use is denied (" + reasonCode.Substring("station-use-denied:".Length) + ")";
				}
				if (reasonCode.StartsWith("local-material-use-denied:", StringComparison.Ordinal))
				{
					return "Runic Crafting: nearby materials OFF by this station's Local Material Use policy (" + reasonCode.Substring("local-material-use-denied:".Length) + ")";
				}
				return "Runic Crafting: nearby materials unavailable (" + reasonCode + ")";
			}
		}

		private static bool TryBuildRequirements(Requirement[] source, int quality, int multiplier, out List<MaterialRequirement> requirements)
		{
			requirements = new List<MaterialRequirement>();
			if (source == null || multiplier <= 0)
			{
				return false;
			}
			try
			{
				foreach (Requirement val in source)
				{
					if ((Object)(object)val?.m_resItem == (Object)null)
					{
						continue;
					}
					int num = checked(val.GetAmount(quality) * multiplier);
					if (num > 0)
					{
						string text = ValheimReflection.ResourceId(val.m_resItem);
						if (text.Length == 0)
						{
							requirements.Clear();
							return false;
						}
						requirements.Add(new MaterialRequirement(text, num));
					}
				}
				return requirements.Count > 0;
			}
			catch (OverflowException)
			{
				requirements.Clear();
				return false;
			}
		}

		private static string FormatMaterialAvailability(IEnumerable<MaterialRequirement> requirements, IEnumerable<MaterialSourceSnapshot> sources)
		{
			SortedDictionary<string, int> sortedDictionary = new SortedDictionary<string, int>(StringComparer.Ordinal);
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			Dictionary<string, int> dictionary2 = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (MaterialRequirement requirement in requirements)
			{
				sortedDictionary.TryGetValue(requirement.ResourceId, out var value);
				sortedDictionary[requirement.ResourceId] = SaturatingAdd(value, requirement.Quantity);
			}
			foreach (MaterialSourceSnapshot source in sources)
			{
				Dictionary<string, int> dictionary3 = ((source.Kind == MaterialSourceKind.PlayerInventory) ? dictionary : dictionary2);
				foreach (string key in sortedDictionary.Keys)
				{
					int right = source.Available(key);
					dictionary3.TryGetValue(key, out var value2);
					dictionary3[key] = SaturatingAdd(value2, right);
				}
			}
			List<string> list = new List<string>(sortedDictionary.Count);
			foreach (KeyValuePair<string, int> item in sortedDictionary)
			{
				dictionary.TryGetValue(item.Key, out var value3);
				dictionary2.TryGetValue(item.Key, out var value4);
				list.Add(item.Key + " required=" + item.Value + " carried=" + value3 + " nearby=" + value4 + " total=" + SaturatingAdd(value3, value4));
			}
			return string.Join("; ", list);
		}

		private static int SaturatingAdd(int left, int right)
		{
			if (left <= int.MaxValue - right)
			{
				return left + right;
			}
			return int.MaxValue;
		}

		private static bool InventorySatisfies(Inventory inventory, IEnumerable<MaterialRequirement> requirements)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			try
			{
				foreach (MaterialRequirement requirement in requirements)
				{
					dictionary.TryGetValue(requirement.ResourceId, out var value);
					dictionary[requirement.ResourceId] = checked(value + requirement.Quantity);
				}
			}
			catch (OverflowException)
			{
				return false;
			}
			foreach (KeyValuePair<string, int> item in dictionary)
			{
				if (ValheimReflection.CountRequirementItems(inventory, item.Key) < item.Value)
				{
					return false;
				}
			}
			return true;
		}

		private static float GetStationRange(CraftingStation station)
		{
			if ((Object)(object)station == (Object)null)
			{
				return Configuration.SafeRangeCap;
			}
			float num = station.GetStationBuildRange();
			if (num <= 0f)
			{
				num = station.m_rangeBuild;
			}
			return Math.Max(1f, Math.Min(Configuration.SafeRangeCap, num));
		}

		private static string StationlessAnchorId(Vector3 origin)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			int num = (int)Math.Floor(origin.x * 2f);
			int num2 = (int)Math.Floor(origin.y * 2f);
			int num3 = (int)Math.Floor(origin.z * 2f);
			return "player-local:" + num + ":" + num2 + ":" + num3;
		}

		private static void CleanupActive(bool success)
		{
			ActiveOperation activeOperation = _activeOperation;
			_activeOperation = null;
			if (activeOperation == null)
			{
				return;
			}
			try
			{
				if (success)
				{
					activeOperation.Lease.Commit();
					CraftingDiagnostics.TraceAction("material-lease", "committed");
				}
				else if (!activeOperation.Lease.Rollback())
				{
					ManualLogSource log = _log;
					if (log != null)
					{
						log.LogError((object)"Runic Crafting rollback could not restore every removed stack. Stop and inspect inventories before continuing.");
					}
				}
				else
				{
					CraftingDiagnostics.TraceAction("material-lease", "rolled-back");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogError((object)("Runic Crafting could not close its material lease safely: " + ex));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class ContainerAwakePatch
	{
		private static void Postfix(Container __instance)
		{
			try
			{
				ContainerSpatialIndex.Register(__instance);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not index a container: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Container), "OnDestroyed")]
	internal static class ContainerDestroyedPatch
	{
		private static void Postfix(Container __instance, bool __runOriginal)
		{
			if (!__runOriginal)
			{
				return;
			}
			try
			{
				ContainerSpatialIndex.Unregister(__instance);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not remove a container index entry: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Container), "CheckForChanges")]
	internal static class ContainerCheckForChangesPatch
	{
		private static void Postfix(Container __instance)
		{
			try
			{
				ContainerSpatialIndex.Register(__instance);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not refresh a container index entry: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(CraftingStation), "Interact")]
	internal static class CraftingStationInteractPatch
	{
		private static bool Prefix(CraftingStation __instance, Humanoid user, ref bool __result)
		{
			try
			{
				Player val = (Player)(object)((user is Player) ? user : null);
				if (val == null || !ValheimReflection.CanMutateLocalPlayer(val))
				{
					return true;
				}
				if (CraftingRuntime.CanUseStation(__instance, val, out var reason))
				{
					return true;
				}
				((Character)val).Message((MessageType)2, "Station use denied: " + reason, 0, (Sprite)null);
				__result = false;
				return false;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("Workshop Access failed closed for this station interaction: " + ex));
				}
				Player val2 = (Player)(object)((user is Player) ? user : null);
				if (val2 != null && ValheimReflection.CanMutateLocalPlayer(val2) && Configuration.Enabled.Value && CraftingRuntime.IsInitialized)
				{
					((Character)val2).Message((MessageType)2, "Station permissions are temporarily unavailable", 0, (Sprite)null);
					__result = false;
					return false;
				}
				return true;
			}
		}

		private static void Postfix(CraftingStation __instance, Humanoid user, bool repeat)
		{
			try
			{
				if (!repeat)
				{
					Player val = (Player)(object)((user is Player) ? user : null);
					if (val != null && val.GetCurrentCraftingStation() == __instance)
					{
						CraftingRuntime.ShowStationStatus(__instance, val);
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not show the crafting station status: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "DoCrafting")]
	internal static class InventoryGuiDoCraftingPatch
	{
		[HarmonyPriority(0)]
		[HarmonyAfter(new string[] { "chazman.RunicInventory" })]
		private static bool Prefix(InventoryGui __instance, Player player, Recipe ___m_craftRecipe, ItemData ___m_craftUpgradeItem, bool ___m_multiCrafting, int ___m_multiCraftAmount)
		{
			try
			{
				return CraftingRuntime.BeforeCraft(__instance, player, ___m_craftRecipe, ___m_craftUpgradeItem, ___m_multiCrafting, ___m_multiCraftAmount);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("Craft preparation failed closed before output creation: " + ex));
				}
				CraftingRuntime.FinishCraft(ex);
				return false;
			}
		}

		private static Exception Finalizer(Exception __exception)
		{
			CraftingRuntime.FinishCraft(__exception);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Inventory), "CanAddItem", new Type[]
	{
		typeof(GameObject),
		typeof(int)
	})]
	internal static class InventoryCanAddCraftOutputPatch
	{
		private static void Postfix(Inventory __instance, GameObject prefab, ref bool __result)
		{
			try
			{
				__result = CraftingRuntime.AfterCraftCapacityCheck(__instance, prefab, __result);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("Craft reservation failed closed at the vanilla capacity check: " + ex));
				}
				CraftingRuntime.CancelPendingCraft();
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(Inventory), "AddItem", new Type[]
	{
		typeof(string),
		typeof(int),
		typeof(int),
		typeof(int),
		typeof(long),
		typeof(string),
		typeof(Vector2i),
		typeof(bool)
	})]
	internal static class InventoryAddCraftOutputPatch
	{
		private static void Postfix(Inventory __instance, ItemData __result)
		{
			try
			{
				CraftingRuntime.CraftOutputAdded(__instance, __result);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not record the completed craft output: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "HaveRequirementItems", new Type[]
	{
		typeof(Recipe),
		typeof(bool),
		typeof(int),
		typeof(int)
	})]
	internal static class PlayerHaveRequirementItemsPatch
	{
		private static bool Prefix(Player __instance, Recipe piece, bool discover, int qualityLevel, int amount, ref bool __result)
		{
			return CraftingRuntime.BeforeHaveRecipeRequirements(__instance, piece, discover, qualityLevel, amount, ref __result);
		}

		private static void Postfix(Player __instance, Recipe piece, bool discover, int qualityLevel, int amount, ref bool __result)
		{
			try
			{
				CraftingRuntime.AddNearbyRecipeAvailability(__instance, piece, discover, qualityLevel, amount, ref __result);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Nearby recipe preview was disabled for this check: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "HaveRequirements", new Type[]
	{
		typeof(Piece),
		typeof(RequirementMode)
	})]
	internal static class PlayerHavePieceRequirementsPatch
	{
		private static void Postfix(Player __instance, Piece piece, RequirementMode mode, ref bool __result)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				CraftingRuntime.AddNearbyPieceAvailability(__instance, piece, mode, ref __result);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Nearby build preview was disabled for this check: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "ConsumeResources", new Type[]
	{
		typeof(Requirement[]),
		typeof(int),
		typeof(int),
		typeof(int)
	})]
	internal static class PlayerConsumeResourcesPatch
	{
		private static bool Prefix(Player __instance, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier)
		{
			return CraftingRuntime.BeforeVanillaConsume(__instance, requirements, qualityLevel, itemQuality, multiplier);
		}
	}
	[HarmonyPatch(typeof(Player), "UpdatePlacement")]
	internal static class PlayerUpdatePlacementPatch
	{
		private static void Prefix()
		{
			CraftingRuntime.BeginPlacementScope();
		}

		private static Exception Finalizer(Exception __exception)
		{
			CraftingRuntime.FinishPlacementScope(__exception);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Player), "PlacePiece", new Type[]
	{
		typeof(Piece),
		typeof(Vector3),
		typeof(Quaternion),
		typeof(bool)
	})]
	internal static class PlayerPlacePieceInstantiationPatch
	{
		private static readonly MethodInfo PlacementCreated = typeof(CraftingRuntime).GetMethod("PlacementObjectInstantiated", BindingFlags.Static | BindingFlags.NonPublic) ?? throw new MissingMethodException(typeof(CraftingRuntime).FullName, "PlacementObjectInstantiated");

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Expected O, but got Unknown
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Expected O, but got Unknown
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			int num = -1;
			for (int i = 0; i < list.Count - 1; i++)
			{
				if (IsExactGameObjectInstantiate(list[i].operand as MethodInfo) && !(list[i + 1].opcode != OpCodes.Stloc_0))
				{
					if (num >= 0)
					{
						throw new InvalidOperationException("Player.PlacePiece contains more than one exact placement Instantiate call.");
					}
					num = i;
				}
			}
			if (num < 0)
			{
				throw new MissingMethodException("Player.PlacePiece no longer contains the audited GameObject Instantiate -> stloc.0 boundary.");
			}
			list.InsertRange(num + 1, (IEnumerable<CodeInstruction>)(object)new CodeInstruction[4]
			{
				new CodeInstruction(OpCodes.Dup, (object)null),
				new CodeInstruction(OpCodes.Ldarg_0, (object)null),
				new CodeInstruction(OpCodes.Ldarg_1, (object)null),
				new CodeInstruction(OpCodes.Call, (object)PlacementCreated)
			});
			return list;
		}

		private static bool IsExactGameObjectInstantiate(MethodInfo method)
		{
			if (method == null || !method.IsGenericMethod || method.DeclaringType != typeof(Object) || !string.Equals(method.Name, "Instantiate", StringComparison.Ordinal))
			{
				return false;
			}
			Type[] genericArguments = method.GetGenericArguments();
			ParameterInfo[] parameters = method.GetParameters();
			if (genericArguments.Length == 1 && genericArguments[0] == typeof(GameObject) && method.ReturnType == typeof(GameObject))
			{
				return parameters.Select((ParameterInfo parameter) => parameter.ParameterType).SequenceEqual(new Type[3]
				{
					typeof(GameObject),
					typeof(Vector3),
					typeof(Quaternion)
				});
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	internal static class PlayerTryPlacePiecePatch
	{
		private static bool Prefix(Player __instance, Piece piece, ref bool __result)
		{
			try
			{
				if (CraftingRuntime.BeforePlacePiece(__instance, piece, out var reason))
				{
					return true;
				}
				((Character)__instance).Message((MessageType)2, "Nearby building cancelled: " + reason, 0, (Sprite)null);
				__result = false;
				return false;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("Build reservation failed closed before placement: " + ex));
				}
				__result = false;
				return false;
			}
		}

		private static void Postfix(bool __result)
		{
			CraftingRuntime.PlacePieceReturned(__result);
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "RepairOneItem")]
	internal static class InventoryGuiRepairOneItemPatch
	{
		private static bool Prefix(InventoryGui __instance)
		{
			try
			{
				return !RepairAllRuntime.TryHandle(__instance);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("Repair All failed closed on the authoritative process: " + ex));
				}
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer != (Object)null && localPlayer == Player.m_localPlayer && ((Character)localPlayer).IsOwner() && Configuration.Enabled.Value && CraftingRuntime.IsInitialized)
				{
					((Character)localPlayer).Message((MessageType)2, "Repair is temporarily unavailable", 0, (Sprite)null);
					return false;
				}
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "SetupRequirement")]
	internal static class InventoryGuiSetupRequirementPatch
	{
		private static void Postfix(Transform elementRoot, Requirement req, Player player, bool craft, int quality, int craftMultiplier)
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!(craft ? CraftingRuntime.TryGetAvailabilityBreakdown(player, CraftingRequirementUiContext.CurrentRecipe, req, quality, craftMultiplier, out var carried, out var nearby, out var required, out var state) : CraftingRuntime.TryGetBuildAvailabilityBreakdown(player, CraftingRequirementUiContext.CurrentPiece, req, out carried, out nearby, out required, out state)))
				{
					return;
				}
				Transform val = elementRoot.Find("res_amount");
				TMP_Text val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<TMP_Text>() : null);
				if (!((Object)(object)val2 == (Object)null))
				{
					val2.text = CraftingRequirementDisplay.FormatCompactAmount(required, carried, nearby, state);
					if (CraftingRequirementDisplay.CombinedTotalSatisfies(required, carried, nearby, state))
					{
						((Graphic)val2).color = Color.white;
					}
					UITooltip component = ((Component)elementRoot).GetComponent<UITooltip>();
					if ((Object)(object)component != (Object)null)
					{
						string text = component.m_text ?? string.Empty;
						string text2 = CraftingRequirementDisplay.FormatTooltipBreakdown(required, carried, nearby, state);
						component.m_text = ((text.Length == 0) ? text2 : (text + "\n" + text2));
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not add carried/nearby requirement counts: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(Hud), "SetupPieceInfo", new Type[] { typeof(Piece) })]
	internal static class HudSetupPieceInfoPatch
	{
		private static void Prefix(Piece piece, out Piece __state)
		{
			__state = CraftingRequirementUiContext.PushPiece(piece);
		}

		private static void Postfix(Piece __state)
		{
			CraftingRequirementUiContext.RestorePiece(__state);
		}

		private static Exception Finalizer(Exception __exception, Piece __state)
		{
			CraftingRequirementUiContext.RestorePiece(__state);
			return __exception;
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "SetupRequirementList", new Type[]
	{
		typeof(int),
		typeof(Player),
		typeof(bool),
		typeof(int)
	})]
	internal static class InventoryGuiSetupRequirementListPatch
	{
		private static void Prefix(InventoryGui __instance, out Recipe __state)
		{
			__state = CraftingRequirementUiContext.Push(__instance);
		}

		private static void Postfix(Recipe __state)
		{
			CraftingRequirementUiContext.Restore(__state);
		}

		private static Exception Finalizer(Exception __exception, Recipe __state)
		{
			CraftingRequirementUiContext.Restore(__state);
			return __exception;
		}
	}
	internal static class RepairAllRuntime
	{
		private static readonly MethodInfo CanRepairMethod = AccessTools.Method(typeof(InventoryGui), "CanRepair", (Type[])null, (Type[])null);

		internal static bool TryHandle(InventoryGui gui)
		{
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!Configuration.Enabled.Value)
			{
				CraftingDiagnostics.TraceAction("repair-all-bypass", "mod-disabled", "vanilla repairs one item");
				return false;
			}
			if (!Configuration.RepairAll.Value)
			{
				CraftingDiagnostics.TraceAction("repair-all-bypass", "repair-all-disabled", "vanilla repairs one item");
				return false;
			}
			if (!CraftingRuntime.IsInitialized)
			{
				CraftingDiagnostics.TraceAction("repair-all-bypass", "runtime-unavailable", "vanilla repairs one item");
				return false;
			}
			if ((Object)(object)localPlayer == (Object)null || localPlayer != Player.m_localPlayer || !((Character)localPlayer).IsOwner())
			{
				CraftingDiagnostics.TraceAction("repair-all-bypass", "local-player-owner-required", "vanilla repairs one item");
				return false;
			}
			CraftingStation currentCraftingStation = localPlayer.GetCurrentCraftingStation();
			if ((Object)(object)currentCraftingStation == (Object)null && !localPlayer.NoCostCheat())
			{
				CraftingDiagnostics.TraceAction("repair-all-bypass", "crafting-station-required", "vanilla handles the repair press");
				return false;
			}
			if ((Object)(object)currentCraftingStation != (Object)null)
			{
				if (!CraftingRuntime.CanUseStation(currentCraftingStation, localPlayer, out var reason))
				{
					((Character)localPlayer).Message((MessageType)2, "Station use denied: " + reason, 0, (Sprite)null);
					CraftingDiagnostics.TraceAction("repair-all-cancelled", "station-use-denied:" + reason);
					return true;
				}
				if (!currentCraftingStation.CheckUsable(localPlayer, false))
				{
					CraftingDiagnostics.TraceAction("repair-all-bypass", "station-not-usable", "vanilla handles the repair press");
					return false;
				}
			}
			if (CanRepairMethod == null)
			{
				CraftingDiagnostics.TraceAction("repair-all-bypass", "valheim-can-repair-method-unavailable", "vanilla repairs one item");
				return false;
			}
			List<ItemData> list = new List<ItemData>();
			Inventory inventory = ((Humanoid)localPlayer).GetInventory();
			inventory.GetWornItems(list);
			int num = 0;
			foreach (ItemData item in list)
			{
				if ((bool)CanRepairMethod.Invoke(gui, new object[1] { item }))
				{
					float maxDurability = item.GetMaxDurability();
					if (!(maxDurability <= 0f) && !(item.m_durability >= maxDurability))
					{
						((Character)localPlayer).RaiseSkill((SkillType)107, 1f - item.m_durability / maxDurability);
						item.m_durability = maxDurability;
						num++;
					}
				}
			}
			if (num == 0)
			{
				((Character)localPlayer).Message((MessageType)2, "No more item to repair", 0, (Sprite)null);
				CraftingDiagnostics.TraceAction("repair-all", "nothing-repairable");
				return true;
			}
			ValheimReflection.NotifyInventoryChanged(inventory);
			if ((Object)(object)currentCraftingStation != (Object)null)
			{
				currentCraftingStation.m_repairItemDoneEffects.Create(((Component)currentCraftingStation).transform.position, Quaternion.identity, (Transform)null, 1f, -1);
			}
			((Character)localPlayer).Message((MessageType)2, $"Repaired {num} items", 0, (Sprite)null);
			CraftingDiagnostics.TraceAction("repair-all", "completed", "items=" + num);
			return true;
		}
	}
	internal sealed class ValheimMaterialSource : IMutableMaterialSource
	{
		private readonly struct Removal
		{
			internal ItemData Original { get; }

			internal int Quantity { get; }

			internal Removal(ItemData original, int quantity)
			{
				Original = original;
				Quantity = quantity;
			}
		}

		private sealed class InventoryRestoreToken : IMaterialRestoreToken
		{
			private readonly Inventory _inventory;

			private readonly ZPackage _snapshot;

			private bool _active = true;

			internal InventoryRestoreToken(Inventory inventory, ZPackage snapshot)
			{
				_inventory = inventory;
				_snapshot = snapshot;
			}

			public bool Restore()
			{
				if (!_active)
				{
					return true;
				}
				try
				{
					ValheimReflection.RestoreInventory(_inventory, _snapshot);
					_active = false;
					return true;
				}
				catch
				{
					return false;
				}
			}
		}

		private readonly Inventory _inventory;

		private readonly MaterialSourceKind _kind;

		private readonly float _distanceSquared;

		private readonly string[] _resourceIds;

		private readonly Func<bool> _eligibility;

		public string SourceId { get; }

		internal bool IsEligible => _eligibility();

		internal ValheimMaterialSource(string sourceId, Inventory inventory, MaterialSourceKind kind, float distanceSquared, IEnumerable<string> resourceIds, Func<bool> eligibility)
		{
			SourceId = sourceId ?? throw new ArgumentNullException("sourceId");
			_inventory = inventory ?? throw new ArgumentNullException("inventory");
			_kind = kind;
			_distanceSquared = distanceSquared;
			_resourceIds = new List<string>(resourceIds ?? throw new ArgumentNullException("resourceIds")).ToArray();
			_eligibility = eligibility ?? throw new ArgumentNullException("eligibility");
		}

		public MaterialSourceSnapshot Snapshot()
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			if (IsEligible)
			{
				string[] resourceIds = _resourceIds;
				foreach (string text in resourceIds)
				{
					dictionary[text] = ValheimReflection.CountRequirementItems(_inventory, text);
				}
			}
			return new MaterialSourceSnapshot(SourceId, _kind, _distanceSquared, dictionary);
		}

		public bool TryTake(string resourceId, int quantity, out IMaterialRestoreToken restoreToken)
		{
			restoreToken = null;
			if (!IsEligible || quantity <= 0)
			{
				return false;
			}
			List<ItemData> allItemsInGridOrder = _inventory.GetAllItemsInGridOrder();
			List<Removal> list = new List<Removal>();
			int num = quantity;
			for (int i = 0; i < allItemsInGridOrder.Count; i++)
			{
				if (num <= 0)
				{
					break;
				}
				ItemData val = allItemsInGridOrder[i];
				if (ValheimReflection.IsUsableRequirementItem(val, resourceId))
				{
					int num2 = Math.Min(val.m_stack, num);
					list.Add(new Removal(val, num2));
					num -= num2;
				}
			}
			if (num != 0)
			{
				return false;
			}
			if (!ValheimReflection.CanRoundTripInventory(_inventory))
			{
				return false;
			}
			ZPackage val2 = ValheimReflection.SaveInventory(_inventory);
			try
			{
				foreach (Removal item in list)
				{
					if (!_inventory.RemoveItem(item.Original, item.Quantity))
					{
						ValheimReflection.RestoreInventory(_inventory, val2);
						return false;
					}
				}
			}
			catch
			{
				ValheimReflection.RestoreInventory(_inventory, val2);
				throw;
			}
			restoreToken = new InventoryRestoreToken(_inventory, val2);
			return true;
		}
	}
	internal static class ValheimReflection
	{
		private static readonly FieldInfo ContainerViewField = AccessTools.Field(typeof(Container), "m_nview");

		private static readonly FieldInfo StationViewField = AccessTools.Field(typeof(CraftingStation), "m_nview");

		private static readonly FieldInfo WardAreasField = AccessTools.Field(typeof(PrivateArea), "m_allAreas");

		private static readonly MethodInfo InventoryChangedMethod = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null);

		private static readonly MethodInfo ContainerCheckAccessMethod = AccessTools.Method(typeof(Container), "CheckAccess", (Type[])null, (Type[])null);

		private static readonly MethodInfo WardIsEnabledMethod = AccessTools.Method(typeof(PrivateArea), "IsEnabled", (Type[])null, (Type[])null);

		private static readonly MethodInfo WardIsInsideMethod = AccessTools.Method(typeof(PrivateArea), "IsInside", (Type[])null, (Type[])null);

		private static readonly FieldInfo SelectedRecipePairField = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe");

		private static readonly FieldInfo SelectedRecipeValueField = SelectedRecipePairField?.FieldType.GetField("<Recipe>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);

		internal static bool CanMutateLocalPlayer(Player player)
		{
			if ((Object)(object)player != (Object)null && player == Player.m_localPlayer)
			{
				return ((Character)player).IsOwner();
			}
			return false;
		}

		internal static Recipe SelectedRecipe(InventoryGui inventoryGui)
		{
			if ((Object)(object)inventoryGui == (Object)null || SelectedRecipePairField == null || SelectedRecipeValueField == null)
			{
				return null;
			}
			try
			{
				object value = SelectedRecipePairField.GetValue(inventoryGui);
				return (Recipe)((value == null) ? null : /*isinst with value type is only supported in some contexts*/);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not resolve Valheim's selected crafting recipe: " + ex.GetType().Name + "."));
				}
				return null;
			}
		}

		internal static ZNetView GetView(Container container)
		{
			if (!((Object)(object)container == (Object)null))
			{
				object? obj = ContainerViewField?.GetValue(container);
				return (ZNetView)((obj is ZNetView) ? obj : null);
			}
			return null;
		}

		internal static ZNetView GetView(CraftingStation station)
		{
			if (!((Object)(object)station == (Object)null))
			{
				object? obj = StationViewField?.GetValue(station);
				return (ZNetView)((obj is ZNetView) ? obj : null);
			}
			return null;
		}

		internal unsafe static string ContainerEndpointId(Container container)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			ZNetView view = GetView(container);
			ZDO val = (((Object)(object)view != (Object)null && view.IsValid()) ? view.GetZDO() : null);
			if (val == null || !val.IsValid())
			{
				return "valheim.instance:" + ((Object)container).GetInstanceID();
			}
			ZDOID ui