Decompiled source of RunicProduction v1.0.0

RunicProduction.dll

Decompiled 9 hours ago
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RunicProduction.Contracts;
using RunicProduction.Core;
using RunicProduction.Integration;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Runic Production")]
[assembly: AssemblyDescription("Standalone native-owner production with persistent links and bounded replenishment")]
[assembly: AssemblyCompany("ChazmanMods")]
[assembly: AssemblyProduct("Runic Production")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: InternalsVisibleTo("RunicProduction.Tests")]
[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 RunicProduction
{
	internal static class ProductionConfig
	{
		internal static ConfigEntry<bool> Enabled { get; private set; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		internal static void Bind(ConfigFile config)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Expected O, but got Unknown
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Expected O, but got Unknown
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Expected O, but got Unknown
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Expected O, but got Unknown
			//IL_028a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0294: Expected O, but got Unknown
			//IL_02e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02eb: Expected O, but got Unknown
			//IL_0312: Unknown result type (might be due to invalid IL or missing references)
			//IL_031c: Expected O, but got Unknown
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable explicit Runic Input, Output, and Replenishment links. Disabling immediately stops automation without leaving operation state behind.");
			MaximumLinkRange = config.Bind<float>("Links", "MaximumLinkRange", 12f, new ConfigDescription("Maximum station-to-container distance in meters.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 30f), Array.Empty<object>()));
			MovedTargetTolerance = config.Bind<float>("Links", "MovedTargetTolerance", 0.75f, new ConfigDescription("Distance a linked target may move from its recorded ZDO-root position before automation pauses. The relation remains available for an explicit refresh.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), Array.Empty<object>()));
			FuelReserve = config.Bind<int>("Fuel", "ProtectedReserve", 10, new ConfigDescription("Minimum fuel items retained in every linked fuel container.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000), Array.Empty<object>()));
			PullBatchSize = config.Bind<int>("Transfers", "PullBatchSize", 1, new ConfigDescription("Maximum items pulled per station update. Capacity and authorization are checked per item.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
			CookingStationPrefabAllowList = config.Bind<string>("Cooking Stations", "AllowedPrefabIds", "piece_cookingstation,piece_cookingstation_iron,piece_oven", "Comma-separated exact prefab IDs allowed to use CookingStation automation. A listed prefab must still pass strict runtime CookingStation compatibility checks.");
			CookingStationPrefabDenyList = config.Bind<string>("Cooking Stations", "DeniedPrefabIds", string.Empty, "Comma-separated exact prefab IDs denied CookingStation automation. Deny entries override the allow list.");
			RecipeStationPrefabAllowList = config.Bind<string>("Recipe Stations", "AllowedPrefabIds", "piece_cauldron,piece_MeadCauldron,piece_preptable", "Comma-separated exact prefab IDs allowed to perform signed replenishment recipes. Every station and recipe must still pass strict runtime compatibility and progression checks.");
			RecipeStationPrefabDenyList = config.Bind<string>("Recipe Stations", "DeniedPrefabIds", string.Empty, "Comma-separated exact recipe-station prefab IDs denied replenishment. Deny entries override the allow list.");
			RecipeNearbyIngredientsEnabled = config.Bind<bool>("Recipe Nearby Ingredients", "Enabled", true, "Use bounded, station-centered discovery of eligible nearby ingredient chests for replenishment recipes, cooking, and fermenting. False preserves exact designated-Input-only behavior. The search radius is Links.MaximumLinkRange. Only loaded, static, accessible chests owned by the same local process qualify; Output, Fuel-compatibility, and Replenishment destinations are excluded unless explicitly linked as Input. Discovery alone never authorizes or mutates a chest.");
			RecipeNearbyMaximumSourceChests = config.Bind<int>("Recipe Nearby Ingredients", "MaximumSourceChests", 32, new ConfigDescription("Maximum eligible nearby ingredient source chests accepted by one replenishment operation. The loaded-container spatial query is deterministic; exceeding this limit or the hard 64-candidate bound pauses production rather than using a partial source set.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 64), Array.Empty<object>()));
			FermenterPrefabAllowList = config.Bind<string>("Fermenters", "AllowedPrefabIds", "fermenter", "Comma-separated exact Fermenter prefab IDs allowed to participate in replenishment production.");
			FermenterPrefabDenyList = config.Bind<string>("Fermenters", "DeniedPrefabIds", string.Empty, "Comma-separated exact Fermenter prefab IDs denied replenishment. Deny entries override the allow list.");
			DefaultIngredientReserve = config.Bind<int>("Ingredient Reserves", "DefaultProtectedReserve", 0, new ConfigDescription("Default count retained for every exact ingredient prefab in a linked Input chest and independently in every eligible nearby donor chest.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000000), Array.Empty<object>()));
			IngredientReserveRules = config.Bind<string>("Ingredient Reserves", "ProtectedPrefabAmounts", string.Empty, "Semicolon-separated, case-sensitive exact prefab reserves, for example DeerMeat=10;RawMeat=20. Nearby production preserves the configured amount separately in every donor; direct recipes may combine one exact batch across several donors. Invalid or duplicate rules fail stock input consumption closed.");
			MaximumLinksPerRole = config.Bind<int>("Links", "MaximumChestsPerRole", 8, new ConfigDescription("Single soft limit for Input, Output, and Replenishment chests per station and role. Existing links are retained if this is lowered. The hard safety limit is 16.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), Array.Empty<object>()));
			DefaultReplenishmentReserve = config.Bind<int>("Replenishment Stock", "DefaultReserve", 10, new ConfigDescription("Default target count maintained for each exemplar item in a linked Replenishment chest. Production runs only while the exact item count is below this reserve.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000000), Array.Empty<object>()));
			ReplenishmentReserveRules = config.Bind<string>("Replenishment Stock", "PrefabReserves", string.Empty, "Semicolon-separated exact prefab reserve overrides, for example QueensJam=20;MeadHealthMinor=10. Invalid or duplicate rules pause replenishment safely.");
			StockSchedulerIntervalSeconds = config.Bind<float>("Stock Scheduler", "IntervalSeconds", 2f, new ConfigDescription("Native-owner fire/lamp and replenishment service quantum. Unloaded time never creates catch-up recipe crafts.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>()));
			StockSchedulerMaximumOperations = config.Bind<int>("Stock Scheduler", "MaximumOperationsPerQuantum", 64, new ConfigDescription("Global cap shared by fire/lamp, direct-recipe, timed-cooking, and Fermenter operations in one scheduler quantum. Each loaded station receives at most one operation per quantum.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 256), Array.Empty<object>()));
			ShowStatusOverlay = config.Bind<bool>("UI", "ShowStatusOverlay", true, "Append the most recent compact automation result to station hover text.");
			ShowControlHints = config.Bind<bool>("UI", "ShowControlHints", true, "Show the two-step Alt+mouse link workflow, Shift+Alt+mouse unlink workflow, current link counts, and empty roles.");
			VerboseLogging = config.Bind<bool>("Diagnostics", "VerboseLogging", false, "Log link and transfer transitions. Per-frame and container-scan logging is never emitted.");
		}
	}
	internal static class ProductionDiagnostics
	{
		private const int MaximumRememberedStations = 2048;

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

		private static ManualLogSource _log;

		internal static bool RuntimeAvailable { get; private set; }

		internal static string DisabledReason { get; private set; }

		internal static void Initialize(ManualLogSource log)
		{
			_log = log;
			RuntimeAvailable = false;
			DisabledReason = "startup has not completed";
			LastStopByStation.Clear();
		}

		internal static void MarkAvailable()
		{
			RuntimeAvailable = true;
			DisabledReason = string.Empty;
		}

		internal static void Disable(string reason, Exception exception = null)
		{
			RuntimeAvailable = false;
			DisabledReason = (string.IsNullOrWhiteSpace(reason) ? "unknown adapter failure" : reason);
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogError((object)("Runic Production disabled its automation; vanilla production remains active. " + DisabledReason + ((exception == null) ? string.Empty : (" " + exception.GetType().Name + ": " + exception.Message))));
			}
		}

		internal static void StopChanged(string stationId, ProductionStopCode stopCode, string detail)
		{
			if (string.IsNullOrEmpty(stationId) || (LastStopByStation.TryGetValue(stationId, out var value) && value == stopCode))
			{
				return;
			}
			if (LastStopByStation.Count >= 2048)
			{
				LastStopByStation.Clear();
			}
			LastStopByStation[stationId] = stopCode;
			ConfigEntry<bool> verboseLogging = ProductionConfig.VerboseLogging;
			if (verboseLogging != null && verboseLogging.Value)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)$"production.stop station={stationId} code={stopCode} detail={detail}");
				}
			}
		}

		internal static bool TryGetStop(string stationId, out ProductionStopCode stopCode)
		{
			stopCode = ProductionStopCode.Ready;
			if (!string.IsNullOrEmpty(stationId))
			{
				return LastStopByStation.TryGetValue(stationId, out stopCode);
			}
			return false;
		}

		internal static void Configuration(string source)
		{
			if (ProductionConfig.Enabled == null)
			{
				return;
			}
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogInfo((object)(string.Format("production.config source={0} enabled={1} ", source ?? "unknown", ProductionConfig.Enabled.Value) + $"range={ProductionConfig.MaximumLinkRange.Value:0.##}m " + $"movedTolerance={ProductionConfig.MovedTargetTolerance.Value:0.##}m " + $"fuelReserve={ProductionConfig.FuelReserve.Value} pullBatch={ProductionConfig.PullBatchSize.Value} " + "cookingAllow='" + SafeConfigText(ProductionConfig.CookingStationPrefabAllowList.Value) + "' cookingDeny='" + SafeConfigText(ProductionConfig.CookingStationPrefabDenyList.Value) + "' recipeAllow='" + SafeConfigText(ProductionConfig.RecipeStationPrefabAllowList.Value) + "' recipeDeny='" + SafeConfigText(ProductionConfig.RecipeStationPrefabDenyList.Value) + "' " + $"recipeNearbyIngredients={ProductionConfig.RecipeNearbyIngredientsEnabled.Value} " + $"recipeNearbyMaxSources={ProductionConfig.RecipeNearbyMaximumSourceChests.Value} " + "fermenterAllow='" + SafeConfigText(ProductionConfig.FermenterPrefabAllowList.Value) + "' fermenterDeny='" + SafeConfigText(ProductionConfig.FermenterPrefabDenyList.Value) + "' " + $"defaultIngredientReserve={ProductionConfig.DefaultIngredientReserve.Value} " + "ingredientReserves='" + SafeConfigText(ProductionConfig.IngredientReserveRules.Value) + "' " + $"maximumChestsPerRole={ProductionConfig.MaximumLinksPerRole.Value} " + $"stockInterval={ProductionConfig.StockSchedulerIntervalSeconds.Value:0.##}s " + $"stockMaxOps={ProductionConfig.StockSchedulerMaximumOperations.Value} " + $"statusOverlay={ProductionConfig.ShowStatusOverlay.Value} " + $"controlHints={ProductionConfig.ShowControlHints.Value} verbose={ProductionConfig.VerboseLogging.Value}"));
			}
			if (ProductionConfig.Enabled.Value && ProductionConfig.FuelReserve.Value >= 100)
			{
				ManualLogSource log2 = _log;
				if (log2 != null)
				{
					log2.LogWarning((object)($"Runic Production fuel reserve is {ProductionConfig.FuelReserve.Value}; " + "a linked container at or below that amount will intentionally supply no fuel."));
				}
			}
		}

		internal static void Trace(string message)
		{
			ConfigEntry<bool> verboseLogging = ProductionConfig.VerboseLogging;
			if (verboseLogging != null && verboseLogging.Value)
			{
				ManualLogSource log = _log;
				if (log != null)
				{
					log.LogInfo((object)("production.input " + (message ?? "unknown")));
				}
			}
		}

		internal static void Info(string message)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogInfo((object)message);
			}
		}

		internal static void Warning(string message)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogWarning((object)message);
			}
		}

		private static string SafeConfigText(string value)
		{
			return new string((value ?? string.Empty).Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ')
				.Take(256)
				.ToArray());
		}
	}
	[BepInPlugin("chazman.RunicProduction", "Runic Production", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "chazman.RunicProduction";

		public const string Name = "Runic Production";

		public const string Version = "1.0.0";

		public const string ModuleId = "runic.production";

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			ProductionDiagnostics.Initialize(((BaseUnityPlugin)this).Logger);
			ProductionConfig.Bind(((BaseUnityPlugin)this).Config);
			((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged;
			ProductionDiagnostics.Configuration("startup");
			try
			{
				ValheimAccess.VerifySignatures();
				ProductionRuntime.Initialize();
				_harmony = new Harmony("chazman.RunicProduction");
				_harmony.PatchAll(typeof(Plugin).Assembly);
				ProductionRuntime.SeedLoadedStations();
				NearbyIngredientContainerIndex.SeedLoadedContainers();
				ProductionDiagnostics.MarkAvailable();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Production v1.0.0 ready: standalone native-owner production, persistent explicit links, and bounded exemplar replenishment.");
			}
			catch (Exception exception)
			{
				Cleanup();
				ProductionDiagnostics.Disable("startup validation failed", exception);
			}
		}

		private void Update()
		{
			if (ProductionDiagnostics.RuntimeAvailable)
			{
				ProductionRuntime.Update();
			}
		}

		private void OnDestroy()
		{
			Cleanup();
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs eventArgs)
		{
			try
			{
				ProductionRuntime.RefreshConfiguration();
				object obj;
				if (eventArgs == null)
				{
					obj = null;
				}
				else
				{
					ConfigEntryBase changedSetting = eventArgs.ChangedSetting;
					obj = ((changedSetting != null) ? ((object)changedSetting.Definition).ToString() : null);
				}
				if (obj == null)
				{
					obj = "setting-change";
				}
				ProductionDiagnostics.Configuration((string)obj);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Runic Production rejected an updated setting: " + ex.Message));
			}
		}

		private void Cleanup()
		{
			((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged;
			NearbyIngredientContainerIndex.Clear();
			ProductionRuntime.Shutdown();
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Runic Production could not remove every Harmony patch: " + ex.Message));
			}
			_harmony = null;
		}
	}
}
namespace RunicProduction.Integration
{
	internal static class CookingStationCompatibility
	{
		internal static bool TryDescribeRegistered(GameObject registeredPrefab, CookingStationPrefabPolicy policy, bool requireAllowed, out CookingStation station, out string prefabId, out string failure)
		{
			station = null;
			prefabId = ValheimAccess.PrefabName(registeredPrefab);
			failure = string.Empty;
			CookingStation[] array = (((Object)(object)registeredPrefab == (Object)null) ? Array.Empty<CookingStation>() : registeredPrefab.GetComponents<CookingStation>());
			ZNetView[] array2 = (((Object)(object)registeredPrefab == (Object)null) ? Array.Empty<ZNetView>() : registeredPrefab.GetComponents<ZNetView>());
			if ((Object)(object)registeredPrefab == (Object)null || prefabId.Length == 0 || array.Length != 1 || array2.Length != 1 || ((Component)array[0]).gameObject != registeredPrefab || ((Component)array2[0]).gameObject != registeredPrefab || (Object)(object)registeredPrefab.GetComponent<WearNTear>() == (Object)null)
			{
				return Fail("CookingStation, ZNetView, and WearNTear require one registered root.", out failure);
			}
			station = array[0];
			if (registeredPrefab.GetComponentsInChildren<Smelter>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Fermenter>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<CraftingStation>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Container>(true).Length != 0)
			{
				return Fail("Hybrid registered CookingStation prefabs are unsupported.", out failure);
			}
			if (requireAllowed && (policy == null || !policy.Allows(prefabId)))
			{
				return Fail("This exact CookingStation prefab ID is not enabled.", out failure);
			}
			if (station.m_slots == null || station.m_slots.Length < 1 || station.m_slots.Length > 64)
			{
				return Fail("CookingStation slot capacity is outside 1-64.", out failure);
			}
			HashSet<int> hashSet = new HashSet<int>();
			Transform[] slots = station.m_slots;
			foreach (Transform val in slots)
			{
				if ((Object)(object)val == (Object)null || !hashSet.Add(((Object)val).GetInstanceID()) || (val != ((Component)station).transform && !val.IsChildOf(((Component)station).transform)) || (Object)(object)((Component)val).GetComponent<ParticleSystem>() == (Object)null || (Object)(object)((Component)val).GetComponent<AudioSource>() == (Object)null)
				{
					return Fail("CookingStation slot transforms are incomplete or ambiguous.", out failure);
				}
			}
			if (station.m_donePS == null || station.m_burntPS == null || (station.m_donePS.Length != 0 && (station.m_donePS.Length < station.m_slots.Length || station.m_donePS.Any((ParticleSystem value) => (Object)(object)value == (Object)null))) || (station.m_burntPS.Length != 0 && (station.m_burntPS.Length < station.m_slots.Length || station.m_burntPS.Any((ParticleSystem value) => (Object)(object)value == (Object)null))))
			{
				return Fail("CookingStation visual arrays do not cover every slot.", out failure);
			}
			if (station.m_conversion == null || station.m_conversion.Count < 1 || station.m_conversion.Count > 256 || (Object)(object)station.m_overCookedItem == (Object)null)
			{
				return Fail("CookingStation conversion metadata is unavailable.", out failure);
			}
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
			HashSet<string> hashSet3 = new HashSet<string>(StringComparer.Ordinal);
			foreach (ItemConversion item in station.m_conversion)
			{
				string text = (((Object)(object)item?.m_from == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_from).gameObject));
				string text2 = (((Object)(object)item?.m_to == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_to).gameObject));
				if (text.Length != 0 && text2.Length != 0 && IsFinitePositive(item.m_cookTime) && hashSet2.Add(text))
				{
					GameObject obj = ValheimAccess.RegisteredItemPrefab(text);
					if (!((Object)(object)((obj != null) ? obj.GetComponent<ItemDrop>() : null) == (Object)null))
					{
						GameObject obj2 = ValheimAccess.RegisteredItemPrefab(text2);
						if (!((Object)(object)((obj2 != null) ? obj2.GetComponent<ItemDrop>() : null) == (Object)null) && !((Object)(object)ValheimAccess.RegisteredItemPrefab(text).transform.Find("attach") == (Object)null) && !((Object)(object)ValheimAccess.RegisteredItemPrefab(text2).transform.Find("attach") == (Object)null))
						{
							hashSet3.Add(text2);
							continue;
						}
					}
				}
				return Fail("CookingStation conversion metadata is invalid or ambiguous.", out failure);
			}
			string text3 = ValheimAccess.PrefabName(((Component)station.m_overCookedItem).gameObject);
			if (text3.Length != 0 && !hashSet2.Overlaps(hashSet3) && !hashSet2.Contains(text3) && !hashSet3.Contains(text3))
			{
				GameObject obj3 = ValheimAccess.RegisteredItemPrefab(text3);
				if (!((Object)(object)((obj3 != null) ? obj3.GetComponent<ItemDrop>() : null) == (Object)null) && !((Object)(object)ValheimAccess.RegisteredItemPrefab(text3).transform.Find("attach") == (Object)null) && station.m_requireFire != station.m_useFuel)
				{
					if (station.m_useFuel && ((Object)(object)station.m_fuelItem == (Object)null || station.m_maxFuel <= 0 || station.m_maxFuel > 100000 || !IsFinitePositive(station.m_secPerFuel)))
					{
						return Fail("CookingStation fuel metadata is invalid.", out failure);
					}
					failure = "ok";
					return true;
				}
			}
			return Fail("CookingStation output/heat metadata is ambiguous.", out failure);
		}

		internal static bool TryValidate(CookingStation station, CookingStationPrefabPolicy policy, bool requireAllowed, out string prefabId, out string failure)
		{
			prefabId = string.Empty;
			failure = string.Empty;
			if ((Object)(object)station == (Object)null || !ValheimAccess.TryGetCookingStationPrefab(station, out prefabId, out var registeredPrefab))
			{
				return Fail("The exact CookingStation prefab identity is unavailable.", out failure);
			}
			if ((Object)(object)((Component)station).gameObject.GetComponent<CookingStation>() != (Object)(object)station || ((Component)station).gameObject.GetComponents<CookingStation>().Length != 1 || (Object)(object)((Component)station).gameObject.GetComponent<ZNetView>() != (Object)(object)ValheimAccess.View(station) || (Object)(object)((Component)station).gameObject.GetComponent<WearNTear>() == (Object)null || registeredPrefab.GetComponents<CookingStation>().Length != 1 || (Object)(object)registeredPrefab.GetComponent<ZNetView>() == (Object)null || (Object)(object)registeredPrefab.GetComponent<WearNTear>() == (Object)null)
			{
				return Fail("CookingStation, ZNetView, and WearNTear must share one unambiguous registered prefab root.", out failure);
			}
			if (((Component)station).GetComponentsInChildren<Smelter>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Smelter>(true).Length != 0 || ((Component)station).GetComponentsInChildren<Fermenter>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Fermenter>(true).Length != 0 || ((Component)station).GetComponentsInChildren<CraftingStation>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<CraftingStation>(true).Length != 0)
			{
				return Fail("Hybrid Smelter, Fermenter, CraftingStation, or CookingStation prefabs are ambiguous and unsupported.", out failure);
			}
			if (((Component)station).GetComponentsInChildren<Container>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Container>(true).Length != 0)
			{
				return Fail("Hybrid Container/CookingStation prefabs make explicit link gestures ambiguous.", out failure);
			}
			if (requireAllowed && (policy == null || !policy.Allows(prefabId)))
			{
				return Fail("This exact CookingStation prefab ID is not enabled by the allow list.", out failure);
			}
			ZNetView val = ValheimAccess.View(station);
			if ((Object)(object)val == (Object)null || !val.IsValid() || ValheimAccess.Zdo(station) == null)
			{
				return Fail("CookingStation network state is unavailable.", out failure);
			}
			if (station.m_slots == null || station.m_slots.Length == 0 || station.m_slots.Length > 64)
			{
				return Fail("CookingStation slot capacity is outside the supported 1-64 range.", out failure);
			}
			HashSet<int> hashSet = new HashSet<int>();
			Transform[] slots = station.m_slots;
			foreach (Transform val2 in slots)
			{
				if ((Object)(object)val2 == (Object)null || !hashSet.Add(((Object)val2).GetInstanceID()) || (val2 != ((Component)station).transform && !val2.IsChildOf(((Component)station).transform)) || (Object)(object)((Component)val2).GetComponent<ParticleSystem>() == (Object)null || (Object)(object)((Component)val2).GetComponent<AudioSource>() == (Object)null)
				{
					return Fail("CookingStation slot transforms are missing or ambiguous.", out failure);
				}
			}
			if (station.m_donePS == null || station.m_burntPS == null || (station.m_donePS.Length != 0 && (station.m_donePS.Length < station.m_slots.Length || station.m_donePS.Any((ParticleSystem value) => (Object)(object)value == (Object)null))) || (station.m_burntPS.Length != 0 && (station.m_burntPS.Length < station.m_slots.Length || station.m_burntPS.Any((ParticleSystem value) => (Object)(object)value == (Object)null))))
			{
				return Fail("CookingStation done/burnt visual arrays do not safely cover every slot.", out failure);
			}
			if (station.m_conversion == null || station.m_conversion.Count == 0 || station.m_conversion.Count > 256)
			{
				return Fail("CookingStation conversion count is outside the supported 1-256 range.", out failure);
			}
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
			HashSet<string> hashSet3 = new HashSet<string>(StringComparer.Ordinal);
			foreach (ItemConversion item in station.m_conversion)
			{
				string text = (((Object)(object)item?.m_from == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_from).gameObject));
				string text2 = (((Object)(object)item?.m_to == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_to).gameObject));
				if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2) && IsFinitePositive(item.m_cookTime) && hashSet2.Add(text))
				{
					GameObject obj = ValheimAccess.RegisteredItemPrefab(text);
					if (!((Object)(object)((obj != null) ? obj.GetComponent<ItemDrop>() : null) == (Object)null))
					{
						GameObject obj2 = ValheimAccess.RegisteredItemPrefab(text2);
						if (!((Object)(object)((obj2 != null) ? obj2.GetComponent<ItemDrop>() : null) == (Object)null))
						{
							if ((Object)(object)ValheimAccess.RegisteredItemPrefab(text).transform.Find("attach") == (Object)null || (Object)(object)ValheimAccess.RegisteredItemPrefab(text2).transform.Find("attach") == (Object)null)
							{
								return Fail("CookingStation item prefabs require vanilla attach visuals.", out failure);
							}
							hashSet3.Add(text2);
							continue;
						}
					}
				}
				return Fail("CookingStation conversions contain a missing, invalid, or ambiguous input mapping.", out failure);
			}
			if (hashSet2.Overlaps(hashSet3))
			{
				return Fail("CookingStation input/output prefab names collide and make vanilla conversion order ambiguous.", out failure);
			}
			string text3 = (((Object)(object)station.m_overCookedItem == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)station.m_overCookedItem).gameObject));
			if (!string.IsNullOrEmpty(text3))
			{
				GameObject obj3 = ValheimAccess.RegisteredItemPrefab(text3);
				if (!((Object)(object)((obj3 != null) ? obj3.GetComponent<ItemDrop>() : null) == (Object)null) && !((Object)(object)ValheimAccess.RegisteredItemPrefab(text3).transform.Find("attach") == (Object)null))
				{
					if (hashSet2.Contains(text3) || hashSet3.Contains(text3))
					{
						return Fail("CookingStation overcooked prefab collides with its conversion table.", out failure);
					}
					if (station.m_requireFire == station.m_useFuel)
					{
						return Fail("CookingStation must use exactly one vanilla heat model: external fire XOR internal fuel.", out failure);
					}
					if (station.m_requireFire && (station.m_fireCheckPoints == null || station.m_fireCheckPoints.Length == 0 || station.m_fireCheckPoints.Length > 64 || !IsFinitePositive(station.m_fireCheckRadius)))
					{
						return Fail("Fire-gated CookingStation fields are incomplete or out of bounds.", out failure);
					}
					if (station.m_requireFire)
					{
						slots = station.m_fireCheckPoints;
						foreach (Transform val3 in slots)
						{
							if ((Object)(object)val3 == (Object)null || (val3 != ((Component)station).transform && !val3.IsChildOf(((Component)station).transform)))
							{
								return Fail("CookingStation fire points must remain inside the registered station root.", out failure);
							}
						}
					}
					if (station.m_useFuel)
					{
						if (!((Object)(object)station.m_fuelItem == (Object)null) && station.m_maxFuel > 0 && station.m_maxFuel <= 100000 && station.m_secPerFuel > 0 && !((Object)(object)station.m_addFuelSwitch == (Object)null))
						{
							GameObject obj4 = ValheimAccess.RegisteredItemPrefab(ValheimAccess.PrefabName(((Component)station.m_fuelItem).gameObject));
							if (!((Object)(object)((obj4 != null) ? obj4.GetComponent<ItemDrop>() : null) == (Object)null))
							{
								goto IL_0650;
							}
						}
						return Fail("Fuelled CookingStation fields are incomplete or out of bounds.", out failure);
					}
					goto IL_0650;
				}
			}
			return Fail("CookingStation has no valid vanilla overcooked item.", out failure);
			IL_0650:
			if (!ControlInside(station, station.m_addFoodSwitch) || !ControlInside(station, station.m_addFuelSwitch))
			{
				return Fail("CookingStation controls must remain inside the registered station root.", out failure);
			}
			return true;
		}

		internal static bool TryValidateState(CookingStation station, out string failure)
		{
			failure = string.Empty;
			if ((Object)(object)station == (Object)null || station.m_slots == null)
			{
				return Fail("CookingStation state is unavailable.", out failure);
			}
			HashSet<string> hashSet = new HashSet<string>(station.m_conversion.Select((ItemConversion conversion) => ValheimAccess.PrefabName(((Component)conversion.m_from).gameObject)), StringComparer.Ordinal);
			HashSet<string> hashSet2 = new HashSet<string>(station.m_conversion.Select((ItemConversion conversion) => ValheimAccess.PrefabName(((Component)conversion.m_to).gameObject)), StringComparer.Ordinal);
			string b = ValheimAccess.PrefabName(((Component)station.m_overCookedItem).gameObject);
			for (int num = 0; num < station.m_slots.Length; num++)
			{
				ValheimAccess.GetCookingSlot(station, num, out var item, out var elapsed, out var status);
				if (float.IsNaN(elapsed) || float.IsInfinity(elapsed) || elapsed < 0f || !Enum.IsDefined(typeof(CookingSlotStatus), status))
				{
					return Fail("CookingStation contains non-finite or invalid slot state.", out failure);
				}
				if (string.IsNullOrEmpty(item))
				{
					if (elapsed != 0f || status != CookingSlotStatus.NotDone)
					{
						return Fail("CookingStation contains a non-canonical empty slot.", out failure);
					}
				}
				else if ((status != CookingSlotStatus.NotDone || !hashSet.Contains(item)) && (status != CookingSlotStatus.Done || !hashSet2.Contains(item)) && (status != CookingSlotStatus.Burnt || !string.Equals(item, b, StringComparison.Ordinal)))
				{
					return Fail("CookingStation slot item/status does not match its exact vanilla conversion table.", out failure);
				}
			}
			if (station.m_useFuel)
			{
				float num2 = ValheimAccess.CookingFuel(station);
				if (float.IsNaN(num2) || float.IsInfinity(num2) || num2 < 0f)
				{
					return Fail("CookingStation fuel state is non-finite or negative.", out failure);
				}
			}
			return true;
		}

		private static bool ControlInside(CookingStation station, Switch control)
		{
			if (!((Object)(object)control == (Object)null) && ((Component)control).transform != ((Component)station).transform)
			{
				return ((Component)control).transform.IsChildOf(((Component)station).transform);
			}
			return true;
		}

		private static bool IsFinitePositive(float value)
		{
			if (value > 0f && !float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		private static bool Fail(string value, out string failure)
		{
			failure = value;
			return false;
		}
	}
	internal sealed class StockSnapshotPublicationException : InvalidOperationException
	{
		internal bool RollbackProven { get; }

		internal StockSnapshotPublicationException(string message, bool rollbackProven, Exception inner)
			: base(message, inner)
		{
			RollbackProven = rollbackProven;
		}
	}
	internal enum StockSourcePreparationFailureKind
	{
		None,
		InvalidInput,
		InvalidRequirements,
		SnapshotUnavailable,
		ReserveProtected,
		IngredientUnavailable
	}
	internal sealed class StockOutputDefinition
	{
		internal string PrefabId { get; }

		internal int Amount { get; }

		internal int Quality { get; }

		internal int Variant { get; }

		internal long CrafterId { get; }

		internal string CrafterName { get; }

		internal StockOutputDefinition(string prefabId, int amount, int quality, int variant, long crafterId, string crafterName)
		{
			if (!StockDomainValidation.IsExactPrefabId(prefabId))
			{
				throw new ArgumentException("An exact output prefab ID is required.", "prefabId");
			}
			if (amount <= 0 || amount > 1000000)
			{
				throw new ArgumentOutOfRangeException("amount");
			}
			if (quality < 1 || quality > 1000)
			{
				throw new ArgumentOutOfRangeException("quality");
			}
			if (variant < 0 || variant > 1000000)
			{
				throw new ArgumentOutOfRangeException("variant");
			}
			PrefabId = prefabId;
			Amount = amount;
			Quality = quality;
			Variant = variant;
			CrafterId = crafterId;
			if (crafterId == 0L)
			{
				if (!string.IsNullOrEmpty(crafterName))
				{
					throw new ArgumentException("Vanilla-default output metadata requires both crafter ID zero and an empty name.", "crafterName");
				}
				CrafterName = string.Empty;
			}
			else
			{
				CrafterName = StockDomainValidation.RequireStableText(crafterName, "crafterName", 128);
			}
		}
	}
	internal sealed class StockInventoryState
	{
		internal const int MaximumPayloadBytes = 262144;

		private readonly byte[] _payload;

		internal string FingerprintValue { get; }

		internal int PayloadLength => _payload.Length;

		internal StockInventoryState(byte[] payload, string fingerprint)
		{
			if (payload == null || payload.Length == 0 || payload.Length > 262144)
			{
				throw new ArgumentOutOfRangeException("payload");
			}
			if (!string.Equals(Fingerprint(payload), fingerprint, StringComparison.Ordinal))
			{
				throw new ArgumentException("The inventory payload does not match its fingerprint.", "fingerprint");
			}
			_payload = (byte[])payload.Clone();
			FingerprintValue = fingerprint;
		}

		internal byte[] CopyPayload()
		{
			return (byte[])_payload.Clone();
		}

		internal bool Matches(Inventory inventory)
		{
			if (inventory != null)
			{
				return string.Equals(FingerprintValue, ExactStockInventoryMutation.Fingerprint(inventory), StringComparison.Ordinal);
			}
			return false;
		}

		internal static StockInventoryState Capture(Inventory inventory)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			if (inventory == null)
			{
				throw new ArgumentNullException("inventory");
			}
			ZPackage val = new ZPackage();
			inventory.Save(val);
			byte[] array = val.GetArray();
			return new StockInventoryState(array, Fingerprint(array));
		}

		internal static string Fingerprint(byte[] payload)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			if (payload == null || payload.Length == 0 || payload.Length > 262144)
			{
				return string.Empty;
			}
			try
			{
				return Convert.ToBase64String(new ZPackage(payload).GenerateHash());
			}
			catch
			{
				return string.Empty;
			}
		}
	}
	internal sealed class StockInventoryTransition
	{
		internal StockInventoryState Before { get; }

		internal StockInventoryState After { get; }

		internal StockInventoryTransition(StockInventoryState before, StockInventoryState after)
		{
			Before = before ?? throw new ArgumentNullException("before");
			After = after ?? throw new ArgumentNullException("after");
			if (string.Equals(before.FingerprintValue, after.FingerprintValue, StringComparison.Ordinal))
			{
				throw new ArgumentException("A positive stock transition must change inventory state.");
			}
		}

		internal bool MatchesBefore(Inventory inventory)
		{
			return Before.Matches(inventory);
		}

		internal bool MatchesAfter(Inventory inventory)
		{
			return After.Matches(inventory);
		}

		internal void ApplyBefore(Inventory inventory)
		{
			ExactStockInventoryMutation.ApplySnapshot(inventory, Before);
		}

		internal void ApplyAfter(Inventory inventory)
		{
			ExactStockInventoryMutation.ApplySnapshot(inventory, After);
		}
	}
	internal sealed class ExactStockMutationPlan
	{
		internal StockInventoryTransition Source { get; }

		internal StockInventoryTransition Destination { get; }

		internal IReadOnlyList<ReplenishmentRequirement> Requirements { get; }

		internal StockOutputDefinition Output { get; }

		internal ExactStockMutationPlan(StockInventoryTransition source, StockInventoryTransition destination, IEnumerable<ReplenishmentRequirement> requirements, StockOutputDefinition output)
		{
			Source = source ?? throw new ArgumentNullException("source");
			Destination = destination ?? throw new ArgumentNullException("destination");
			List<ReplenishmentRequirement> list = new List<ReplenishmentRequirement>();
			if (requirements != null)
			{
				list.AddRange(requirements);
			}
			if (list.Count == 0 || list.Any((ReplenishmentRequirement requirement) => requirement == null))
			{
				throw new ArgumentException("A complete exact stock plan requires positive requirements.", "requirements");
			}
			Requirements = new ReadOnlyCollection<ReplenishmentRequirement>(list);
			Output = output ?? throw new ArgumentNullException("output");
		}
	}
	internal sealed class ExactStockStagingPlan
	{
		internal int SourceIndex { get; }

		internal string SourceId { get; }

		internal string PrefabId { get; }

		internal int Amount { get; }

		internal StockInventoryTransition Source { get; }

		internal StockInventoryTransition Destination { get; }

		internal NearbyIngredientStagingPlan AggregatePlan { get; }

		internal ExactStockStagingPlan(int sourceIndex, string sourceId, string prefabId, int amount, StockInventoryTransition source, StockInventoryTransition destination, NearbyIngredientStagingPlan aggregatePlan)
		{
			if (sourceIndex < 0)
			{
				throw new ArgumentOutOfRangeException("sourceIndex");
			}
			SourceId = StockDomainValidation.RequireStableText(sourceId, "sourceId", 200);
			if (!StockDomainValidation.IsExactPrefabId(prefabId))
			{
				throw new ArgumentException("An exact staged prefab is required.", "prefabId");
			}
			if (amount <= 0 || amount > 1000000)
			{
				throw new ArgumentOutOfRangeException("amount");
			}
			SourceIndex = sourceIndex;
			PrefabId = prefabId;
			Amount = amount;
			Source = source ?? throw new ArgumentNullException("source");
			Destination = destination ?? throw new ArgumentNullException("destination");
			AggregatePlan = aggregatePlan ?? throw new ArgumentNullException("aggregatePlan");
		}
	}
	internal sealed class ExactStockCompositeSourceEntry
	{
		internal int SourceIndex { get; }

		internal StockInventoryTransition Transition { get; }

		internal ExactStockCompositeSourceEntry(int sourceIndex, StockInventoryTransition transition)
		{
			if (sourceIndex < 0)
			{
				throw new ArgumentOutOfRangeException("sourceIndex");
			}
			SourceIndex = sourceIndex;
			Transition = transition ?? throw new ArgumentNullException("transition");
		}
	}
	internal sealed class ExactStockCompositeSourcePlan
	{
		internal int SourceCount { get; }

		internal IReadOnlyList<ExactStockCompositeSourceEntry> Entries { get; }

		internal ExactStockCompositeSourcePlan(int sourceCount, IEnumerable<ExactStockCompositeSourceEntry> entries)
		{
			if (sourceCount < 1 || sourceCount > 64)
			{
				throw new ArgumentOutOfRangeException("sourceCount");
			}
			List<ExactStockCompositeSourceEntry> list = entries?.ToList() ?? throw new ArgumentNullException("entries");
			if (list.Count == 0 || list.Count > sourceCount || list.Any((ExactStockCompositeSourceEntry value) => value == null || value.SourceIndex >= sourceCount) || list.Select((ExactStockCompositeSourceEntry value) => value.SourceIndex).Distinct().Count() != list.Count)
			{
				throw new ArgumentException("Composite source transitions must be unique and bounded.", "entries");
			}
			SourceCount = sourceCount;
			Entries = new ReadOnlyCollection<ExactStockCompositeSourceEntry>(list);
		}
	}
	internal static class ExactStockInventoryMutation
	{
		private static readonly MethodInfo InventoryAddAtMethod = AccessTools.Method(typeof(Inventory), "AddItem", new Type[4]
		{
			typeof(ItemData),
			typeof(int),
			typeof(int),
			typeof(int)
		}, (Type[])null) ?? throw new MissingMethodException(typeof(Inventory).FullName, "AddItem(ItemData,int,int,int)");

		internal static bool TryPrepare(Inventory source, Inventory destination, IEnumerable<ReplenishmentRequirement> requirements, IngredientReservePolicy reserves, StockOutputDefinition output, out ExactStockMutationPlan plan, out string failure)
		{
			plan = null;
			failure = string.Empty;
			if (!TryPrepareSource(source, requirements, reserves, out var transition, out var normalizedRequirements, out failure))
			{
				return false;
			}
			if (!TryPrepareDestination(destination, output, out var transition2, out failure))
			{
				return false;
			}
			plan = new ExactStockMutationPlan(transition, transition2, normalizedRequirements, output);
			return true;
		}

		internal static bool TryPrepareSource(Inventory source, IEnumerable<ReplenishmentRequirement> requirements, IngredientReservePolicy reserves, out StockInventoryTransition transition, out IReadOnlyList<ReplenishmentRequirement> normalizedRequirements, out string failure)
		{
			StockSourcePreparationFailureKind failureKind;
			return TryPrepareSourceDetailed(source, requirements, reserves, out transition, out normalizedRequirements, out failureKind, out failure);
		}

		internal static bool TryPrepareSourceDetailed(Inventory source, IEnumerable<ReplenishmentRequirement> requirements, IngredientReservePolicy reserves, out StockInventoryTransition transition, out IReadOnlyList<ReplenishmentRequirement> normalizedRequirements, out StockSourcePreparationFailureKind failureKind, out string failure)
		{
			transition = null;
			normalizedRequirements = Array.Empty<ReplenishmentRequirement>();
			failureKind = StockSourcePreparationFailureKind.None;
			failure = string.Empty;
			if (source == null || reserves == null || !reserves.IsValid)
			{
				failureKind = StockSourcePreparationFailureKind.InvalidInput;
				failure = "stock.source-or-reserve-policy-unavailable";
				return false;
			}
			if (!TryNormalizeRequirements(requirements, out var normalized, out failure))
			{
				failureKind = StockSourcePreparationFailureKind.InvalidRequirements;
				return false;
			}
			StockInventoryState stockInventoryState = StockInventoryState.Capture(source);
			Inventory val;
			try
			{
				val = Clone(source, stockInventoryState);
			}
			catch
			{
				failureKind = StockSourcePreparationFailureKind.SnapshotUnavailable;
				failure = "stock.source-snapshot-not-roundtrippable";
				return false;
			}
			foreach (ReplenishmentRequirement item in normalized)
			{
				int num = CountExact(val, item.PrefabId);
				if (!reserves.AllowsConsumption(item.PrefabId, num, item.Amount))
				{
					failureKind = StockSourcePreparationFailureKind.ReserveProtected;
					failure = "stock.ingredient-reserve-protected:" + item.PrefabId;
					return false;
				}
				if (!RemoveExact(val, item.PrefabId, item.Amount))
				{
					failureKind = StockSourcePreparationFailureKind.IngredientUnavailable;
					failure = "stock.ingredient-unavailable:" + item.PrefabId;
					return false;
				}
			}
			StockInventoryState stockInventoryState2 = StockInventoryState.Capture(val);
			try
			{
				Clone(val, stockInventoryState2);
			}
			catch
			{
				failureKind = StockSourcePreparationFailureKind.SnapshotUnavailable;
				failure = "stock.source-result-not-roundtrippable";
				return false;
			}
			transition = new StockInventoryTransition(stockInventoryState, stockInventoryState2);
			normalizedRequirements = new ReadOnlyCollection<ReplenishmentRequirement>(normalized);
			return true;
		}

		internal static bool TryPrepareDestination(Inventory destination, StockOutputDefinition output, out StockInventoryTransition transition, out string failure)
		{
			transition = null;
			failure = string.Empty;
			if (destination == null || output == null)
			{
				failure = "stock.destination-or-output-unavailable";
				return false;
			}
			ObjectDB instance = ObjectDB.instance;
			GameObject val = ((instance != null) ? instance.GetItemPrefab(output.PrefabId) : null);
			ItemDrop val2 = ((val != null) ? val.GetComponent<ItemDrop>() : null);
			if ((Object)(object)val2 == (Object)null || !string.Equals(((Object)val).name, output.PrefabId, StringComparison.Ordinal))
			{
				failure = "stock.output-prefab-unavailable";
				return false;
			}
			StockInventoryState stockInventoryState = StockInventoryState.Capture(destination);
			Inventory val3;
			try
			{
				val3 = Clone(destination, stockInventoryState);
			}
			catch
			{
				failure = "stock.destination-snapshot-not-roundtrippable";
				return false;
			}
			int num = CountExact(val3, output.PrefabId, matchWorldLevel: false);
			if (!TryAddExactOutput(val3, val, val2, output))
			{
				failure = "stock.complete-output-batch-does-not-fit";
				return false;
			}
			if (CountExact(val3, output.PrefabId, matchWorldLevel: false) - num != output.Amount)
			{
				failure = "stock.output-metadata-or-count-mismatch";
				return false;
			}
			StockInventoryState stockInventoryState2 = StockInventoryState.Capture(val3);
			try
			{
				Clone(val3, stockInventoryState2);
			}
			catch
			{
				failure = "stock.destination-result-not-roundtrippable";
				return false;
			}
			transition = new StockInventoryTransition(stockInventoryState, stockInventoryState2);
			return true;
		}

		internal static bool TryPrepareCompositeSources(IReadOnlyList<Inventory> orderedSources, IEnumerable<ReplenishmentRequirement> requirements, IngredientReservePolicy reserves, out ExactStockCompositeSourcePlan plan, out string failure)
		{
			plan = null;
			failure = string.Empty;
			if (orderedSources == null || orderedSources.Count == 0 || orderedSources.Count > 64 || reserves == null || !reserves.IsValid || orderedSources.Any((Inventory value) => value == null) || orderedSources.Distinct().Count() != orderedSources.Count)
			{
				failure = "stock.composite-sources-invalid";
				return false;
			}
			if (!TryNormalizeRequirements(requirements, out var normalized, out failure))
			{
				return false;
			}
			try
			{
				StockInventoryState[] array = new StockInventoryState[orderedSources.Count];
				Inventory[] array2 = (Inventory[])(object)new Inventory[orderedSources.Count];
				bool[] array3 = new bool[orderedSources.Count];
				for (int num = 0; num < orderedSources.Count; num++)
				{
					array[num] = StockInventoryState.Capture(orderedSources[num]);
					array2[num] = Clone(orderedSources[num], array[num]);
				}
				foreach (ReplenishmentRequirement item in normalized)
				{
					int num2 = item.Amount;
					for (int num3 = 0; num3 < array2.Length; num3++)
					{
						if (num2 <= 0)
						{
							break;
						}
						int num4 = CountExact(array2[num3], item.PrefabId);
						if (!reserves.TryGetProtectedReserve(item.PrefabId, out var protectedReserve))
						{
							failure = "stock.composite-reserve-unavailable:" + item.PrefabId;
							return false;
						}
						int val = Math.Max(0, num4 - protectedReserve);
						int num5 = Math.Min(num2, val);
						if (num5 != 0)
						{
							if (!RemoveExact(array2[num3], item.PrefabId, num5))
							{
								failure = "stock.composite-remove-failed:" + item.PrefabId;
								return false;
							}
							array3[num3] = true;
							num2 -= num5;
						}
					}
					if (num2 != 0)
					{
						failure = "stock.composite-ingredient-unavailable:" + item.PrefabId;
						return false;
					}
				}
				List<ExactStockCompositeSourceEntry> list = new List<ExactStockCompositeSourceEntry>();
				for (int num6 = 0; num6 < array2.Length; num6++)
				{
					if (array3[num6])
					{
						StockInventoryState stockInventoryState = StockInventoryState.Capture(array2[num6]);
						Clone(array2[num6], stockInventoryState);
						list.Add(new ExactStockCompositeSourceEntry(num6, new StockInventoryTransition(array[num6], stockInventoryState)));
					}
				}
				plan = new ExactStockCompositeSourcePlan(orderedSources.Count, list);
				return true;
			}
			catch (Exception ex)
			{
				failure = "stock.composite-snapshot-failure:" + ex.GetType().Name;
				plan = null;
				return false;
			}
		}

		internal static bool TryPrepareStaging(Inventory input, IReadOnlyList<Inventory> orderedSources, IReadOnlyList<string> orderedSourceIds, IEnumerable<ReplenishmentRequirement> requirements, IngredientReservePolicy reserves, int pullBatchSize, out ExactStockStagingPlan plan, out string failure)
		{
			plan = null;
			failure = string.Empty;
			if (input == null || orderedSources == null || orderedSourceIds == null || orderedSources.Count == 0 || orderedSources.Count != orderedSourceIds.Count || orderedSources.Count > 64 || reserves == null || !reserves.IsValid || pullBatchSize <= 0 || pullBatchSize > 10)
			{
				failure = "nearby-stage.inventory-input-invalid";
				return false;
			}
			List<StockInventoryState> list = new List<StockInventoryState>(orderedSources.Count);
			List<Inventory> list2 = new List<Inventory>(orderedSources.Count);
			List<NearbyIngredientSourceStock> list3 = new List<NearbyIngredientSourceStock>(orderedSources.Count);
			HashSet<Inventory> hashSet = new HashSet<Inventory>();
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
			List<ReplenishmentRequirement> list4 = requirements?.Take(17).ToList();
			if (list4 == null || list4.Count == 0 || list4.Count > 16 || list4.Any((ReplenishmentRequirement value) => value == null))
			{
				failure = "nearby-stage.requirements-invalid";
				return false;
			}
			try
			{
				StockInventoryState stockInventoryState = StockInventoryState.Capture(input);
				Inventory val = Clone(input, stockInventoryState);
				IReadOnlyDictionary<string, int> inputExactCounts = ExactCounts(input, list4);
				for (int num = 0; num < orderedSources.Count; num++)
				{
					Inventory val2 = orderedSources[num];
					string text = orderedSourceIds[num];
					if (val2 == null || val2 == input || !hashSet.Add(val2) || !hashSet2.Add(text ?? string.Empty))
					{
						failure = "nearby-stage.source-alias-or-duplicate";
						return false;
					}
					StockInventoryState stockInventoryState2 = StockInventoryState.Capture(val2);
					list.Add(stockInventoryState2);
					list2.Add(Clone(val2, stockInventoryState2));
					list3.Add(new NearbyIngredientSourceStock(text, ExactCounts(val2, list4)));
				}
				if (!NearbyIngredientStagingPlanner.TryPlan(list4, inputExactCounts, list3, reserves, pullBatchSize, out var plan2, out failure))
				{
					return false;
				}
				foreach (NearbyIngredientStagingContribution contribution in plan2.Contributions)
				{
					if (contribution.SourceIndex < 0 || contribution.SourceIndex >= list2.Count || !TryMoveExactPreservingMetadata(list2[contribution.SourceIndex], val, contribution.PrefabId, contribution.Amount))
					{
						failure = "nearby-stage.complete-batch-does-not-fit-input";
						return false;
					}
				}
				Clone(val, StockInventoryState.Capture(val));
				foreach (Inventory item in list2)
				{
					Clone(item, StockInventoryState.Capture(item));
				}
				NearbyIngredientStagingContribution nextContribution = plan2.NextContribution;
				Inventory val3 = Clone(orderedSources[nextContribution.SourceIndex], list[nextContribution.SourceIndex]);
				Inventory val4 = Clone(input, stockInventoryState);
				int num2 = CountExact(val3, nextContribution.PrefabId);
				int num3 = CountExact(val4, nextContribution.PrefabId);
				if (!TryMoveExactPreservingMetadata(val3, val4, nextContribution.PrefabId, plan2.NextTransferAmount) || num2 - CountExact(val3, nextContribution.PrefabId) != plan2.NextTransferAmount || CountExact(val4, nextContribution.PrefabId) - num3 != plan2.NextTransferAmount)
				{
					failure = "nearby-stage.exact-transfer-could-not-be-modeled";
					return false;
				}
				StockInventoryState stockInventoryState3 = StockInventoryState.Capture(val3);
				StockInventoryState stockInventoryState4 = StockInventoryState.Capture(val4);
				Clone(val3, stockInventoryState3);
				Clone(val4, stockInventoryState4);
				plan = new ExactStockStagingPlan(nextContribution.SourceIndex, nextContribution.SourceId, nextContribution.PrefabId, plan2.NextTransferAmount, new StockInventoryTransition(list[nextContribution.SourceIndex], stockInventoryState3), new StockInventoryTransition(stockInventoryState, stockInventoryState4), plan2);
				return true;
			}
			catch (Exception ex)
			{
				failure = "nearby-stage.snapshot-or-metadata-failure:" + ex.GetType().Name;
				plan = null;
				return false;
			}
		}

		internal static void ApplySnapshot(Inventory inventory, StockInventoryState state)
		{
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			if (inventory == null)
			{
				throw new ArgumentNullException("inventory");
			}
			if (state == null)
			{
				throw new ArgumentNullException("state");
			}
			Clone(inventory, state);
			StockInventoryState stockInventoryState = StockInventoryState.Capture(inventory);
			Clone(inventory, stockInventoryState);
			Action onChanged = inventory.m_onChanged;
			Exception ex2;
			try
			{
				inventory.m_onChanged = null;
				inventory.Load(new ZPackage(state.CopyPayload()));
				if (!state.Matches(inventory))
				{
					throw new InvalidOperationException("The exact inventory snapshot did not apply.");
				}
				inventory.m_onChanged = onChanged;
				onChanged?.Invoke();
				if (!state.Matches(inventory))
				{
					throw new InvalidOperationException("The exact inventory snapshot changed during publication.");
				}
				return;
			}
			catch (Exception ex)
			{
				ex2 = ex;
			}
			finally
			{
				inventory.m_onChanged = onChanged;
			}
			Exception ex3 = null;
			bool flag = false;
			try
			{
				inventory.m_onChanged = null;
				inventory.Load(new ZPackage(stockInventoryState.CopyPayload()));
				if (!stockInventoryState.Matches(inventory))
				{
					throw new InvalidOperationException("The original inventory snapshot did not restore.");
				}
				inventory.m_onChanged = onChanged;
				onChanged?.Invoke();
				if (!stockInventoryState.Matches(inventory))
				{
					throw new InvalidOperationException("The original inventory changed during rollback publication.");
				}
				flag = true;
			}
			catch (Exception ex4)
			{
				ex3 = ex4;
			}
			finally
			{
				inventory.m_onChanged = onChanged;
			}
			throw new StockSnapshotPublicationException(flag ? "The exact inventory publication failed; the original live state was restored." : "The exact inventory publication failed and rollback is indeterminate.", flag, (ex3 == null) ? ex2 : new AggregateException(ex2, ex3));
		}

		internal static string Fingerprint(Inventory inventory)
		{
			return StockInventoryState.Capture(inventory).FingerprintValue;
		}

		internal static int CountExact(Inventory inventory, string prefabId, bool matchWorldLevel = true)
		{
			if (inventory == null || !StockDomainValidation.IsExactPrefabId(prefabId))
			{
				return 0;
			}
			long num = 0L;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (string.Equals(ValheimAccess.PrefabName(allItem), prefabId, StringComparison.Ordinal) && (!matchWorldLevel || allItem.m_worldLevel >= Game.m_worldLevel))
				{
					num += Math.Max(0, allItem.m_stack);
					if (num >= int.MaxValue)
					{
						return int.MaxValue;
					}
				}
			}
			return (int)num;
		}

		private static IReadOnlyDictionary<string, int> ExactCounts(Inventory inventory, IEnumerable<ReplenishmentRequirement> requirements)
		{
			SortedDictionary<string, int> sortedDictionary = new SortedDictionary<string, int>(StringComparer.Ordinal);
			foreach (ReplenishmentRequirement requirement in requirements)
			{
				if (!sortedDictionary.ContainsKey(requirement.PrefabId))
				{
					sortedDictionary.Add(requirement.PrefabId, CountExact(inventory, requirement.PrefabId));
				}
			}
			return sortedDictionary;
		}

		private static bool TryNormalizeRequirements(IEnumerable<ReplenishmentRequirement> requirements, out List<ReplenishmentRequirement> normalized, out string failure)
		{
			normalized = new List<ReplenishmentRequirement>();
			failure = string.Empty;
			SortedDictionary<string, long> sortedDictionary = new SortedDictionary<string, long>(StringComparer.Ordinal);
			if (requirements != null)
			{
				int num = 0;
				foreach (ReplenishmentRequirement requirement in requirements)
				{
					if (requirement == null || ++num > 16)
					{
						failure = "stock.requirements-invalid-or-unbounded";
						return false;
					}
					sortedDictionary.TryGetValue(requirement.PrefabId, out var value);
					long num2 = value + requirement.Amount;
					if (num2 <= 0 || num2 > 1000000)
					{
						failure = "stock.requirement-amount-out-of-range";
						return false;
					}
					sortedDictionary[requirement.PrefabId] = num2;
				}
			}
			if (sortedDictionary.Count == 0)
			{
				failure = "stock.requirements-empty";
				return false;
			}
			foreach (KeyValuePair<string, long> item in sortedDictionary)
			{
				normalized.Add(new ReplenishmentRequirement(item.Key, (int)item.Value));
			}
			return true;
		}

		private static bool RemoveExact(Inventory inventory, string prefabId, int amount)
		{
			List<ItemData> list = (from item in inventory.GetAllItems()
				where string.Equals(ValheimAccess.PrefabName(item), prefabId, StringComparison.Ordinal) && item.m_worldLevel >= Game.m_worldLevel
				orderby item.m_gridPos.y, item.m_gridPos.x, item.m_quality
				select item).ToList();
			int num = amount;
			foreach (ItemData item in list)
			{
				int num2 = Math.Min(Math.Max(0, item.m_stack), num);
				if (num2 > 0)
				{
					if (!inventory.RemoveItem(item, num2))
					{
						return false;
					}
					num -= num2;
					if (num == 0)
					{
						return true;
					}
				}
			}
			return false;
		}

		private static bool TryMoveExactPreservingMetadata(Inventory source, Inventory destination, string prefabId, int amount)
		{
			if (source == null || destination == null || source == destination || !StockDomainValidation.IsExactPrefabId(prefabId) || amount <= 0)
			{
				return false;
			}
			List<ItemData> list = (from item in source.GetAllItems()
				where string.Equals(ValheimAccess.PrefabName(item), prefabId, StringComparison.Ordinal) && item.m_worldLevel >= Game.m_worldLevel && item.m_stack > 0
				orderby item.m_gridPos.y, item.m_gridPos.x, item.m_quality
				select item).ToList();
			int num = amount;
			foreach (ItemData item in list)
			{
				int num2 = Math.Min(item.m_stack, num);
				if (num2 > 0)
				{
					ItemData val = item.Clone();
					val.m_stack = num2;
					if (!TryAddExactPreservingMetadata(destination, val, num2) || !source.RemoveItem(item, num2))
					{
						return false;
					}
					num -= num2;
					if (num == 0)
					{
						return true;
					}
				}
			}
			return false;
		}

		private static bool TryAddExactPreservingMetadata(Inventory inventory, ItemData template, int amount)
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			if (inventory == null || template?.m_shared == null || (Object)(object)template.m_dropPrefab == (Object)null || amount <= 0)
			{
				return false;
			}
			int maximumStack = template.m_shared.m_maxStackSize;
			if (maximumStack <= 0)
			{
				return false;
			}
			List<ItemData> list = (from candidate in inventory.GetAllItems()
				where ExactPersistentMetadataEquals(template, candidate) && candidate.m_stack >= 0 && candidate.m_stack < maximumStack
				orderby candidate.m_gridPos.y, candidate.m_gridPos.x
				select candidate).ToList();
			List<Vector2i> list2 = new List<Vector2i>();
			for (int num = 0; num < inventory.GetHeight(); num++)
			{
				for (int num2 = 0; num2 < inventory.GetWidth(); num2++)
				{
					if (inventory.GetItemAt(num2, num) == null)
					{
						list2.Add(new Vector2i(num2, num));
					}
				}
			}
			if (list.Sum((ItemData target) => (long)maximumStack - (long)target.m_stack) + (long)list2.Count * (long)maximumStack < amount)
			{
				return false;
			}
			int num3 = amount;
			foreach (ItemData item in list)
			{
				if (num3 == 0)
				{
					break;
				}
				int num4 = Math.Min(num3, maximumStack - item.m_stack);
				if (!AddExactAt(inventory, template, num4, item.m_gridPos))
				{
					return false;
				}
				num3 -= num4;
			}
			foreach (Vector2i item2 in list2)
			{
				if (num3 == 0)
				{
					break;
				}
				int num5 = Math.Min(num3, maximumStack);
				if (!AddExactAt(inventory, template, num5, item2))
				{
					return false;
				}
				num3 -= num5;
			}
			return num3 == 0;
		}

		private static bool TryAddExactOutput(Inventory inventory, GameObject outputPrefab, ItemDrop drop, StockOutputDefinition output)
		{
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
			if (inventory == null || (Object)(object)outputPrefab == (Object)null || drop?.m_itemData?.m_shared == null)
			{
				return false;
			}
			ItemData template = drop.m_itemData.Clone();
			template.m_dropPrefab = outputPrefab;
			template.m_stack = 1;
			template.m_quality = output.Quality;
			template.m_variant = output.Variant;
			template.m_crafterID = output.CrafterId;
			template.m_crafterName = output.CrafterName;
			template.m_worldLevel = (byte)Game.m_worldLevel;
			template.m_pickedUp = false;
			template.m_equipped = false;
			template.m_durability = template.GetMaxDurability();
			int maximumStack = template.m_shared.m_maxStackSize;
			if (maximumStack <= 0)
			{
				return false;
			}
			List<ItemData> list = (from candidate in inventory.GetAllItems()
				where ExactPersistentMetadataEquals(template, candidate) && candidate.m_stack >= 0 && candidate.m_stack < maximumStack
				orderby candidate.m_gridPos.y, candidate.m_gridPos.x
				select candidate).ToList();
			List<Vector2i> list2 = new List<Vector2i>();
			for (int num = 0; num < inventory.GetHeight(); num++)
			{
				for (int num2 = 0; num2 < inventory.GetWidth(); num2++)
				{
					if (inventory.GetItemAt(num2, num) == null)
					{
						list2.Add(new Vector2i(num2, num));
					}
				}
			}
			long num3 = 0L;
			foreach (ItemData item in list)
			{
				num3 += maximumStack - item.m_stack;
			}
			num3 += (long)list2.Count * (long)maximumStack;
			if (num3 < output.Amount)
			{
				return false;
			}
			int num4 = output.Amount;
			foreach (ItemData item2 in list)
			{
				if (num4 == 0)
				{
					break;
				}
				int num5 = Math.Min(num4, maximumStack - item2.m_stack);
				if (!AddExactAt(inventory, template, num5, item2.m_gridPos))
				{
					return false;
				}
				num4 -= num5;
			}
			foreach (Vector2i item3 in list2)
			{
				if (num4 == 0)
				{
					break;
				}
				int num6 = Math.Min(num4, maximumStack);
				if (!AddExactAt(inventory, template, num6, item3))
				{
					return false;
				}
				num4 -= num6;
			}
			return num4 == 0;
		}

		private static bool AddExactAt(Inventory inventory, ItemData template, int amount, Vector2i slot)
		{
			//IL_002f: 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)
			if (amount <= 0)
			{
				return false;
			}
			ItemData val = template.Clone();
			val.m_stack = amount;
			if ((bool)InventoryAddAtMethod.Invoke(inventory, new object[4] { val, amount, slot.x, slot.y }))
			{
				return val.m_stack == 0;
			}
			return false;
		}

		private static bool ExactPersistentMetadataEquals(ItemData left, ItemData right)
		{
			if (left == null || right == null || !string.Equals(ValheimAccess.PrefabName(left), ValheimAccess.PrefabName(right), StringComparison.Ordinal) || left.m_quality != right.m_quality || left.m_variant != right.m_variant || left.m_worldLevel != right.m_worldLevel || left.m_crafterID != right.m_crafterID || !string.Equals(left.m_crafterName ?? string.Empty, right.m_crafterName ?? string.Empty, StringComparison.Ordinal) || left.m_pickedUp != right.m_pickedUp || left.m_equipped != right.m_equipped || !left.m_durability.Equals(right.m_durability))
			{
				return false;
			}
			return DictionaryEquals(left.m_customData, right.m_customData);
		}

		private static bool DictionaryEquals(IDictionary<string, string> left, IDictionary<string, string> right)
		{
			int num = left?.Count ?? 0;
			if (num != (right?.Count ?? 0))
			{
				return false;
			}
			if (num == 0)
			{
				return true;
			}
			foreach (KeyValuePair<string, string> item in left)
			{
				if (!right.TryGetValue(item.Key, out var value) || !string.Equals(item.Value, value, StringComparison.Ordinal))
				{
					return false;
				}
			}
			return true;
		}

		private static Inventory Clone(Inventory source, StockInventoryState state)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			Inventory val = new Inventory(source.GetName(), (Sprite)null, source.GetWidth(), source.GetHeight());
			val.Load(new ZPackage(state.CopyPayload()));
			if (!state.Matches(val))
			{
				throw new InvalidOperationException("The serialized inventory snapshot is not an exact round trip under the current item definitions.");
			}
			return val;
		}
	}
	internal sealed class FermenterConversionDescriptor
	{
		internal string InputPrefabId { get; }

		internal string OutputPrefabId { get; }

		internal int OutputAmount { get; }

		internal FermenterConversionDescriptor(string inputPrefabId, string outputPrefabId, int outputAmount)
		{
			InputPrefabId = inputPrefabId;
			OutputPrefabId = outputPrefabId;
			OutputAmount = outputAmount;
		}
	}
	internal sealed class FermenterDescriptor
	{
		private readonly ReadOnlyCollection<FermenterConversionDescriptor> _conversions;

		private readonly Dictionary<string, FermenterConversionDescriptor> _byInput;

		private readonly Dictionary<string, FermenterConversionDescriptor> _byOutput;

		internal string PrefabId { get; }

		internal IReadOnlyList<FermenterConversionDescriptor> Conversions => _conversions;

		internal FermenterDescriptor(string prefabId, IEnumerable<FermenterConversionDescriptor> conversions)
		{
			PrefabId = prefabId;
			List<FermenterConversionDescriptor> list = conversions.OrderBy<FermenterConversionDescriptor, string>((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal).ToList();
			_conversions = list.AsReadOnly();
			_byInput = list.ToDictionary<FermenterConversionDescriptor, string>((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal);
			_byOutput = list.ToDictionary<FermenterConversionDescriptor, string>((FermenterConversionDescriptor value) => value.OutputPrefabId, StringComparer.Ordinal);
		}

		internal bool TryFromInput(string prefabId, out FermenterConversionDescriptor conversion)
		{
			return _byInput.TryGetValue(prefabId ?? string.Empty, out conversion);
		}

		internal bool TryFromOutput(string prefabId, out FermenterConversionDescriptor conversion)
		{
			return _byOutput.TryGetValue(prefabId ?? string.Empty, out conversion);
		}
	}
	internal static class FermenterCompatibility
	{
		private const float VanillaFermentationSeconds = 2400f;

		private const float DurationTolerance = 0.01f;

		private const int MaximumConversions = 256;

		internal static bool TryValidate(Fermenter station, FermenterPrefabPolicy policy, bool requireAllowed, out FermenterDescriptor descriptor, out string failure)
		{
			descriptor = null;
			failure = string.Empty;
			if ((Object)(object)station == (Object)null || !ValheimAccess.TryGetFermenterPrefab(station, out var prefabId, out var registeredPrefab))
			{
				return Fail("The exact registered Fermenter prefab identity is unavailable.", out failure);
			}
			if (requireAllowed && (policy == null || !policy.Allows(prefabId)))
			{
				return Fail("This exact Fermenter prefab ID is not enabled by the local allow list.", out failure);
			}
			if (!RootIsExact(((Component)station).gameObject, station, ValheimAccess.View(station)) || !RegisteredRootIsExact(registeredPrefab))
			{
				return Fail("Fermenter, ZNetView, and WearNTear must be unique components on one registered prefab root.", out failure);
			}
			if ((Object)(object)((Component)station).gameObject.GetComponent<Smelter>() != (Object)null || (Object)(object)((Component)station).gameObject.GetComponent<CookingStation>() != (Object)null || ((Component)station).GetComponentsInChildren<CraftingStation>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<CraftingStation>(true).Length != 0 || ((Component)station).GetComponentsInChildren<Container>(true).Length != 0 || registeredPrefab.GetComponentsInChildren<Container>(true).Length != 0)
			{
				return Fail("Hybrid production/container Fermenter roots are ambiguous and unsupported.", out failure);
			}
			ZNetView val = ValheimAccess.View(station);
			if ((Object)(object)val == (Object)null || !val.IsValid() || ValheimAccess.Zdo(station) == null)
			{
				return Fail("Fermenter network state is unavailable.", out failure);
			}
			if (!IsFinite(station.m_fermentationDuration) || Math.Abs(station.m_fermentationDuration - 2400f) > 0.01f)
			{
				return Fail("Fermenter duration must retain vanilla's exact 2400-second model.", out failure);
			}
			if (!IsFinite(station.m_tapDelay) || station.m_tapDelay < 0f || station.m_tapDelay > 30f)
			{
				return Fail("Fermenter tap delay is non-finite or outside the supported vanilla range.", out failure);
			}
			if (!ControlInside(station, station.m_addSwitch) || !ControlInside(station, station.m_tapSwitch) || !TransformInside(station, station.m_outputPoint) || !TransformInside(station, station.m_roofCheckPoint) || (Object)(object)station.m_addSwitch == (Object)null || (Object)(object)station.m_tapSwitch == (Object)null || (Object)(object)station.m_outputPoint == (Object)null || (Object)(object)station.m_roofCheckPoint == (Object)null)
			{
				return Fail("Fermenter controls, output, and cover points must remain inside its exact root.", out failure);
			}
			if (!TryReadConversions(station.m_conversion, out var descriptors, out failure))
			{
				return false;
			}
			Fermenter component = registeredPrefab.GetComponent<Fermenter>();
			if ((Object)(object)component == (Object)null || !IsFinite(component.m_fermentationDuration) || Math.Abs(component.m_fermentationDuration - 2400f) > 0.01f || !IsFinite(component.m_tapDelay) || component.m_tapDelay < 0f || component.m_tapDelay > 30f || (Object)(object)component.m_addSwitch == (Object)null || (Object)(object)component.m_tapSwitch == (Object)null || (Object)(object)component.m_outputPoint == (Object)null || (Object)(object)component.m_roofCheckPoint == (Object)null || !ControlInside(component, component.m_addSwitch) || !ControlInside(component, component.m_tapSwitch) || !TransformInside(component, component.m_outputPoint) || !TransformInside(component, component.m_roofCheckPoint) || !TryReadConversions(component.m_conversion, out var descriptors2, out failure) || !SameConversions(descriptors, descriptors2))
			{
				return Fail("The live Fermenter no longer matches its exact registered vanilla-compatible definition.", out failure);
			}
			descriptor = new FermenterDescriptor(prefabId, descriptors);
			return true;
		}

		internal static bool TryValidateState(Fermenter station, FermenterDescriptor descriptor, out FermenterStationState state, out string failure)
		{
			state = null;
			failure = string.Empty;
			if ((Object)(object)station == (Object)null || descriptor == null || !FermenterStationState.TryCapture(station, out state))
			{
				return Fail("Fermenter content/start state is non-canonical.", out failure);
			}
			if (!state.IsEmpty && !descriptor.TryFromInput(state.InputPrefabId, out var _))
			{
				return Fail("Fermenter content does not match its exact conversion table.", out failure);
			}
			if (ValheimAccess.FermenterDelayedTapActive(station))
			{
				return Fail("A vanilla DelayedTap is active; links and refresh must wait for it to complete.", out failure);
			}
			return true;
		}

		internal static bool TryDescribeRegistered(Fermenter registered, string prefabId, out FermenterDescriptor descriptor, out string failure)
		{
			descriptor = null;
			failure = string.Empty;
			if ((Object)(object)registered == (Object)null || !StockDomainValidation.IsExactPrefabId(prefabId) || !RegisteredRootIsExact(((Component)registered).gameObject) || (Object)(object)((Component)registered).gameObject.GetComponent<Smelter>() != (Object)null || (Object)(object)((Component)registered).gameObject.GetComponent<CookingStation>() != (Object)null || ((Component)registered).GetComponentsInChildren<CraftingStation>(true).Length != 0 || ((Component)registered).GetComponentsInChildren<Container>(true).Length != 0)
			{
				return Fail("The registered Fermenter producer root is structurally ambiguous.", out failure);
			}
			if (!IsFinite(registered.m_fermentationDuration) || Math.Abs(registered.m_fermentationDuration - 2400f) > 0.01f || !IsFinite(registered.m_tapDelay) || registered.m_tapDelay < 0f || registered.m_tapDelay > 30f || (Object)(object)registered.m_addSwitch == (Object)null || (Object)(object)registered.m_tapSwitch == (Object)null || (Object)(object)registered.m_outputPoint == (Object)null || (Object)(object)registered.m_roofCheckPoint == (Object)null || !ControlInside(registered, registered.m_addSwitch) || !ControlInside(registered, registered.m_tapSwitch) || !TransformInside(registered, registered.m_outputPoint) || !TransformInside(registered, registered.m_roofCheckPoint) || !TryReadConversions(registered.m_conversion, out var descriptors, out failure))
			{
				return false;
			}
			descriptor = new FermenterDescriptor(prefabId, descriptors);
			failure = string.Empty;
			return true;
		}

		private static bool TryReadConversions(List<ItemConversion> source, out List<FermenterConversionDescriptor> descriptors, out string failure)
		{
			descriptors = new List<FermenterConversionDescriptor>();
			failure = string.Empty;
			if (source == null || source.Count == 0 || source.Count > 256)
			{
				return Fail("Fermenter conversion count is outside the supported 1-256 range.", out failure);
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
			foreach (ItemConversion item in source)
			{
				string text = (((Object)(object)item?.m_from == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_from).gameObject));
				string text2 = (((Object)(object)item?.m_to == (Object)null) ? string.Empty : ValheimAccess.PrefabName(((Component)item.m_to).gameObject));
				int num = item?.m_producedItems ?? 0;
				if (StockDomainValidation.IsExactPrefabId(text) && StockDomainValidation.IsExactPrefabId(text2) && hashSet.Add(text) && hashSet2.Add(text2) && (num == 3 || num == 6))
				{
					GameObject obj = ValheimAccess.RegisteredItemPrefab(text);
					if (!((Object)(object)((obj != null) ? obj.GetComponent<ItemDrop>() : null) == (Object)null))
					{
						GameObject obj2 = ValheimAccess.RegisteredItemPrefab(text2);
						if (!((Object)(object)((obj2 != null) ? obj2.GetComponent<ItemDrop>() : null) == (Object)null))
						{
							descriptors.Add(new FermenterConversionDescriptor(text, text2, num));
							continue;
						}
					}
				}
				return Fail("Fermenter conversions require unique exact inputs/outputs and complete vanilla 3/6-item batches.", out failure);
			}
			if (hashSet.Overlaps(hashSet2))
			{
				return Fail("Fermenter input/output prefab IDs collide and are ambiguous.", out failure);
			}
			return true;
		}

		private static bool RootIsExact(GameObject root, Fermenter station, ZNetView expectedView)
		{
			if ((Object)(object)root != (Object)null && root.GetComponentsInChildren<Fermenter>(true).Length == 1 && root.GetComponent<Fermenter>() == station && root.GetComponentsInChildren<ZNetView>(true).Length == 1 && root.GetComponent<ZNetView>() == expectedView && root.GetComponentsInChildren<WearNTear>(true).Length == 1)
			{
				return (Object)(object)root.GetComponent<WearNTear>() != (Object)null;
			}
			return false;
		}

		private static bool RegisteredRootIsExact(GameObject root)
		{
			if ((Object)(object)root != (Object)null && root.GetComponentsInChildren<Fermenter>(true).Length == 1 && (Object)(object)root.GetComponent<Fermenter>() != (Object)null && root.GetComponentsInChildren<ZNetView>(true).Length == 1 && (Object)(object)root.GetComponent<ZNetView>() != (Object)null && root.GetComponentsInChildren<WearNTear>(true).Length == 1)
			{
				return (Object)(object)root.GetComponent<WearNTear>() != (Object)null;
			}
			return false;
		}

		private static bool ControlInside(Fermenter station, Switch control)
		{
			if ((Object)(object)control != (Object)null)
			{
				return TransformInside(station, ((Component)control).transform);
			}
			return false;
		}

		private static bool TransformInside(Fermenter station, Transform value)
		{
			if ((Object)(object)value != (Object)null)
			{
				if (value != ((Component)station).transform)
				{
					return value.IsChildOf(((Component)station).transform);
				}
				return true;
			}
			return false;
		}

		private static bool IsFinite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}

		private static bool SameConversions(IEnumerable<FermenterConversionDescriptor> left, IEnumerable<FermenterConversionDescriptor> right)
		{
			FermenterConversionDescriptor[] array = left.OrderBy<FermenterConversionDescriptor, string>((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal).ToArray();
			FermenterConversionDescriptor[] array2 = right.OrderBy<FermenterConversionDescriptor, string>((FermenterConversionDescriptor value) => value.InputPrefabId, StringComparer.Ordinal).ToArray();
			if (array.Length != array2.Length)
			{
				return false;
			}
			for (int num = 0; num < array.Length; num++)
			{
				if (!string.Equals(array[num].InputPrefabId, array2[num].InputPrefabId, StringComparison.Ordinal) || !string.Equals(array[num].OutputPrefabId, array2[num].OutputPrefabId, StringComparison.Ordinal) || array[num].OutputAmount != array2[num].OutputAmount)
				{
					return false;
				}
			}
			return true;
		}

		private static bool Fail(string value, out string failure)
		{
			failure = value;
			return false;
		}
	}
	internal static class FermenterPlanBuilder
	{
		internal static bool TryBuild(FermenterDescriptor descriptor, Inventory replenishmentInventory, StoredProductionLink link, string stationId, long actorId, string actorName, out ReplenishmentPlan plan, out string failure)
		{
			return TryBuild(descriptor, replenishmentInventory, link, stationId, actorId, actorName, null, out plan, out failure);
		}

		internal static bool TryBuild(FermenterDescriptor descriptor, Inventory replenishmentInventory, StoredProductionLink link, string stationId, long actorId, string actorName, ReplenishmentPlan previous, out ReplenishmentPlan plan, out string failure)
		{
			plan = null;
			failure = string.Empty;
			if (descriptor == null || replenishmentInventory == null || link == null || string.IsNullOrEmpty(stationId) || actorId == 0L || string.IsNullOrWhiteSpace(actorName))
			{
				return Fail("Fermenter plan inputs are unavailable.", out failure);
			}
			bool flag = previous != null && previous.AdapterKind == ReplenishmentProducerKind.Fermenter && string.Equals(previous.StationPrefabId, descriptor.PrefabId, StringComparison.Ordinal) && ReplenishmentPlanStore.MatchesLink(previous, link, stationId);
			if (previous != null && !flag)
			{
				return Fail("The prior Fermenter plan is not bound to this exact station and link.", out failure);
			}
			long num = (flag ? previous.AuthorizedPlayerId : actorId);
			string text = (flag ? previous.AuthorizedPlayerName : actorName);
			if (num == 0L || num != link.OwnerId || string.IsNullOrWhiteSpace(text))
			{
				return Fail("The Fermenter plan principal does not match the exact link owner.", out failure);
			}
			SortedSet<string> sortedSet = new SortedSet<string>(StringComparer.Ordinal);
			foreach (ItemData allItem in replenishmentInventory.GetAllItems())
			{
				string text2 = ValheimAccess.PrefabName(allItem);
				if (allItem != null && allItem.m_stack > 0 && descriptor.TryFromOutput(text2, out var _))
				{
					sortedSet.Add(text2);
					if (sortedSet.Count > 32)
					{
						return Fail("The replenishment chest contains too many Fermenter output exemplars.", out failure);
					}
				}
			}
			if (flag && previous.Revision == int.MaxValue)
			{
				return Fail("The Fermenter plan revision is exhausted; remove and re-add this destination.", out failure);
			}
			List<ReplenishmentTargetAuthorization> list = new List<ReplenishmentTargetAuthorization>(sortedSet.Count);
			foreach (string item in sortedSet)
			{
				if (!descriptor.TryFromOutput(item, out var conversion2))
				{
					return Fail("A Fermenter output exemplar became ambiguous.", out failure);
				}
				list.Add(new ReplenishmentTargetAuthorization(conversion2.OutputPrefabId, ReplenishmentProducerKind.Fermenter, FermenterProducerSignature.ProducerId(descriptor, conversion2), FermenterProducerSignature.Create(descriptor, conversion2), conversion2.OutputAmount, string.Empty, 0, num, text, new ReplenishmentRequirement[1]
				{
					new ReplenishmentRequirement(conversion2.InputPrefabId, 1)
				}));
			}
			string priorTarget = ((flag && previous.Targets.Count > 0) ? previous.Targets[previous.Cursor].OutputPrefabId : string.Empty);
			int cursor = 0;
			if (priorTarget.Length != 0)
			{
				int num2 = list.FindIndex((ReplenishmentTargetAuthorization value) => string.Equals(value.OutputPrefabId, priorTarget, StringComparison.Ordinal));
				if (num2 >= 0)
				{
					cursor = num2;
				}
			}
			plan = new ReplenishmentPlan(flag ? previous.PlanId : Guid.NewGuid().ToString("N"), (!flag) ? 1 : (previous.Revision + 1), cursor, descriptor.PrefabId, ReplenishmentProducerKind.Fermenter, ReplenishmentPlanStore.Bind(link, stationId), num, text, list);
			return true;
		}

		internal static bool TryValidate(ReplenishmentPlan plan, FermenterDescriptor descriptor, StoredProductionLink link, string stationId, Inventory replenishmentInventory, out string failure)
		{
			failure = string.Empty;
			if (plan == null || descriptor == null || link == null || replenishmentInventory == null || plan.AdapterKind != ReplenishmentProducerKind.Fermenter || !string.Equals(plan.StationPrefabId, descriptor.PrefabId, StringComparison.Ordinal) || !ReplenishmentPlanStore.MatchesLink(plan, link, stationId) || plan.AuthorizedPlayerId != link.OwnerId)
			{
				return Fail("The persisted Fermenter plan is not bound to this exact station/link.", out failure);
			}
			foreach (ReplenishmentTargetAuthorization target in plan.Targets)
			{
				if (target.ProducerKind != ReplenishmentProducerKind.Fermenter || target.AuthorizedPlayerId != link.OwnerId || target.Requirements.Count != 1 || target.Requirements[0].Amount != 1 || !descriptor.TryFromOutput(target.OutputPrefabId, out var conversion) || !string.Equals(target.Requirements[0].PrefabId, conversion.InputPrefabId, StringComparison.Ordinal) || target.OutputAmount != conversion.OutputAmount || !string.Equals(target.ProducerId, FermenterProducerSignature.ProducerId(descriptor, conversion), StringComparison.Ordinal) || !target.ProducerSignatureMatches(FermenterProducerSignature.Create(descriptor, conversion)))
				{
					return Fail("A Fermenter target no longer matches the exact producer signature.", out failure);
				}
			}
			return true;
		}

		private static bool Fail(string value, out string failure)
		{
			failure = value;
			return false;
		}
	}
	internal static class FermenterProducerSignature
	{
		private const int SchemaVersion = 1;

		internal static string ProducerId(FermenterDescriptor station, FermenterConversionDescriptor conversion)
		{
			if (station == null)
			{
				throw new ArgumentNullException("station");
			}
			if (conversion == null)
			{
				throw new ArgumentNullException("conversion");
			}
			return "fermenter:" + station.PrefabId + ":" + conversion.InputPrefabId;
		}

		internal static byte[] Create(FermenterDescriptor station, FermenterConversionDescriptor conversion)
		{
			if (station == null)
			{
				throw new ArgumentNullException("station");
			}
			if (conversion == null)
			{
				throw new ArgumentNullException("conversion");
			}
			return StockProducerSignature.Create(delegate(StockSignatureWriter writer)
			{
				writer.WriteString("RunicProduction.Fermenter");
				writer.WriteInt32(1);
				writer.WriteString(station.PrefabId);
				writer.WriteSingle(2400f);
				writer.WriteString(conversion.InputPrefabId);
				writer.WriteString(conversion.OutputPrefabId);
				writer.WriteInt32(conversion.OutputAmount);
			});
		}
	}
	internal sealed class FermenterStationState
	{
		private const int SchemaVersion = 1;

		private const int MaximumEncodedCharacters = 512;

		internal string InputPrefabId { get; }

		internal long StartTicks { get; }

		internal bool IsEmpty => InputPrefabId.Length == 0;

		internal static FermenterStationState Empty { get; } = new FermenterStationState(string.Empty, 0L);

		private FermenterStationState(string inputPrefabId, long startTicks)
		{
			InputPrefabId = inputPrefabId;
			StartTicks = startTicks;
		}

		internal static bool TryCreate(string inputPrefabId, long startTicks, out FermenterStationState state)
		{
			state = null;
			string text = inputPrefabId ?? string.Empty;
			if (text.Length == 0)
			{
				if (startTicks != 0L)
				{
					return false;
				}
				state = Empty;
				return true;
			}
			if (!StockDomainValidation.IsExactPrefabId(text) || startTicks <= 0)
			{
				return false;
			}
			try
			{
				if (new DateTime(startTicks).Kind == DateTimeKind.Local)
				{
					return false;
				}
			}
			catch (ArgumentOutOfRangeException)
			{
				return false;
			}
			state = new FermenterStationState(text, startTicks);
			return true;
		}

		internal static bool TryCapture(Fermenter station, out FermenterStationState state)
		{
			state = null;
			if ((Object)(object)station != (Object)null)
			{
				return TryCreate(ValheimAccess.FermenterContent(station), ValheimAccess.FermenterStartTicks(station), out state);
			}
			return false;
		}

		internal static bool TryCapture(ZDO zdo, out FermenterStationState state)
		{
			state = null;
			if (zdo != null && zdo.IsValid())
			{
				return TryCreate(zdo.GetString(ZDOVars.s_content, string.Empty), zdo.GetLong(ZDOVars.s_startTime, 0L), out state);
			}
			return false;
		}

		internal string Serialize()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(InputPrefabId);
			val.Write(StartTicks);
			return Convert.ToBase64String(val.GetArray());
		}

		internal static bool TryParse(string encoded, out FermenterStationState state)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			state = null;
			if (string.IsNullOrEmpty(encoded) || encoded.Length > 512)
			{
				return false;
			}
			try
			{
				byte[] array = Convert.FromBase64String(encoded);
				if (array.Length == 0 || array.Length > 256)
				{
					return false;
				}
				ZPackage val = new ZPackage(array);
				if (val.ReadInt() != 1)
				{
					return false;
				}
				string inputPrefabId = val.ReadString();
				long startTicks = val.ReadLong();
				if (val.GetPos() != val.Size())
				{
					return false;
				}
				return TryCreate(inputPrefabId, startTicks, out state) && string.Equals(state.Serialize(), encoded, StringComparison.Ordinal);
			}
			catch
			{
				return false;
			}
		}

		internal bool Matches(Fermenter station)
		{
			if ((Object)(object)station != (Object)null && string.Equals(InputPrefabId, ValheimAccess.FermenterContent(station), StringComparison.Ordinal))
			{
				return StartTicks == ValheimAccess.FermenterStartTicks(station);
			}
			return false;
		}

		internal void Apply(Fermenter station)
		{
			if ((Object)(object)station == (Object)null)
			{
				throw new ArgumentNullException("station");
			}
			ValheimAccess.SetFermenterState(station, InputPrefabId, StartTicks);
		}

		internal void ApplyToZdo(ZDO zdo)
		{
			if (zdo == null || !zdo.IsValid())
			{
				throw new ArgumentNullException("zdo");
			}
			zdo.Set(ZDOVars.s_content, InputPrefabId);
			zdo.Set(ZDOVars.s_startTime, StartTicks);
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class ProductionPlayerUpdateInputPatch
	{
		[HarmonyPriority(800)]
		[HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicBuildCamera" })]
		private static void Prefix(Player __instance)
		{
			try
			{
				ProductionRuntime.TickLocalPlayer(__instance);
			}
			catch (Exception exception)
			{
				ProductionRuntime.FailHook("Player.Update link input", exception);
			}
		}
	}
	[HarmonyPatch(typeof(ZInput), "GetButton", new Type[] { typeof(string) })]
	internal static class ProductionConsumedButtonPatch
	{
		[HarmonyPriority(800)]
		[HarmonyAfter(new string[] { "chazman.RunicStorage" })]
		[HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })]
		private static bool Prefix(string name, ref bool __result)
		{
			if (!ProductionLinkInput.ShouldSuppress(name, Time.frameCount))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(ZInput), "GetButtonDown", new Type[] { typeof(string) })]
	internal static class ProductionConsumedButtonDownPatch
	{
		[HarmonyPriority(800)]
		[HarmonyAfter(new string[] { "chazman.RunicStorage" })]
		[HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })]
		private static bool Prefix(string name, ref bool __result)
		{
			if (!ProductionLinkInput.ShouldSuppress(name, Time.frameCount))
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	[HarmonyPatch]
	internal static class ProductionSetControlsInputPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(Player), "SetControls", new Type[12]
			{
				typeof(Vector3),
				typeof(bool),
				typeof(bool),
				typeof(bool),
				typeof(bool),
				typeof(bool),
				typeof(bool),
				typeof(bool),
				typeof(bool),
				typeof(bool),
				typeof(bool),
				typeof(bool)
			}, (Type[])null);
		}

		[HarmonyPriority(800)]
		[HarmonyBefore(new string[] { "chazman.RunicBuildCamera", "chazman.RunicInventory" })]
		private static void Prefix(Player __instance, ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold, ref bool block, ref bool blockHold)
		{
			try
			{
				ProductionRuntime.TickLocalPlayer(__instance);
			}
			catch (Exception exception)
			{
				ProductionRuntime.FailHook("Player.SetControls link input", exception);
			}
			ProductionLinkInput.SuppressSetControls(__instance, Time.frameCount, ref attack, ref attackHold, ref secondaryAttack, ref secondaryAttackHold, ref block, ref blockHold);
		}
	}
	[HarmonyPatch(typeof(Smelter), "Awake")]
	internal static class ProductionSmelterAwakePatch
	{
		private static void Postfix(Smelter __instance)
		{
			try
			{
				ProductionRuntime.Register(__instance);
			}
			catch (Exception exception)
			{
				ProductionRuntime.FailHook("Smelter.Awake", exception);
			}
		}
	}
	[HarmonyPatch(typeof(CookingStation), "Awake")]
	internal static class ProductionCookingAwakePatch
	{
		private static void Postfix(CookingStation __instance)
		{
			try
			{
				ProductionRuntime.Register(__instance);
			}
			catch (Exception exception)
			{
				ProductionRuntime.FailHook("CookingStation.Awake", exception);
			}
		}
	}
	[HarmonyPatch(typeof(CraftingStation), "Start")]
	internal static class ProductionRecipeStartPatch
	{
		private static void Postfix(CraftingStation __instance)
		{
			try
			{
				ProductionRuntime.Register(__instance);
			}
			catch (Exception exception)
			{
				ProductionRuntime.FailHook("CraftingStation.Start", exception);
			}
		}
	}
	[HarmonyPatch(typeof(Fermenter), "Awake")]
	internal static class ProductionFermenterAwakePatch
	{
		private static void Postfix(Fermenter __instance)
		{
			try
			{
				ProductionRuntime.Register(__instance);
			}
			catch (Exception exception)
			{
				ProductionRuntime.FailHook("Fermenter.Awake", exception);
			}
		}
	}
	[HarmonyPatch(typeof(Fireplace), "Awake")]
	internal static class ProductionFireplaceAwakePatch
	{
		private static void Postfix(Fireplace __instance)
		{
			try
			{
				ProductionRuntime.Register(__instance);
			}
			catch (Exception exception)
			{
				ProductionRuntime.FailHook("Fireplace.Awake", exception);
			}
		}
	}
	[HarmonyPatch(typeof(Smelter), "Spawn")]
	internal static class ProductionSmelterSpawnPatch
	{
		private static bool Prefix(Smelter __instance, string ore, int stack)
		{
			try
			{
				return !ProductionRuntime.TryDepositProducedOutput(__instance, ore, stack);
			}
			catch (Exception exception)
			{
				ProductionRuntime.FailStation((Component)(object)__instance, "Smelter.Spawn", exception);
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(Smelter), "OnHoverAddOre")]
	internal static class ProductionSmelterInputHoverPatch
	{
		private static void Postfix(Smelter __instance, ref string __result)
		{
			ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Input, ref __result);
		}
	}
	[HarmonyPatch(typeof(Smelter), "OnHoverAddFuel")]
	internal static class ProductionSmelterFuelHoverPatch
	{
		private static void Postfix(Smelter __instance, ref string __result)
		{
			ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Fuel, ref __result);
		}
	}
	[HarmonyPatch(typeof(Smelter), "OnHoverEmptyOre")]
	internal static class ProductionSmelterOutputHoverPatch
	{
		private static void Postfix(Smelter __instance, ref string __result)
		{
			ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Output, ref __result);
		}
	}
	[HarmonyPatch(typeof(CookingStation), "GetHoverText")]
	internal static class ProductionCookingHoverPatch
	{
		private static void Postfix(CookingStation __instance, ref string __result)
		{
			ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Input, ref __result);
		}
	}
	[HarmonyPatch(typeof(CraftingStation), "GetHoverText")]
	internal static class ProductionRecipeHoverPatch
	{
		private static void Postfix(CraftingStation __instance, ref string __result)
		{
			ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Input, ref __result);
		}
	}
	[HarmonyPatch(typeof(Fermenter), "GetHoverText")]
	internal static class ProductionFermenterHoverPatch
	{
		private static void Postfix(Fermenter __instance, ref string __result)
		{
			ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Input, ref __result);
		}
	}
	[HarmonyPatch(typeof(Fireplace), "GetHoverText")]
	internal static class ProductionFireplaceHoverPatch
	{
		private static void Postfix(Fireplace __instance, ref string __result)
		{
			ProductionRuntime.AppendHover((Component)(object)__instance, ProductionLinkRole.Fuel, ref __result);
		}
	}
	[HarmonyPatch(typeof(Player), "Interact", new Type[]
	{
		typeof(GameObject),
		typeof(bool),
		typeof(bool)
	})]
	[HarmonyPriority(800)]
	internal static class ProductionPlayerInteractPatch
	{
		private static bool Prefix(Player __instance, GameObject go, bool hold, bool alt)
		{
			try
			{
				if (!ProductionRuntime.TryHandleInteraction(__instance, go, hold, alt, out var _))
				{
					return true;
				}
				return false;
			}
			catch (Exception exception)
			{
				ProductionRuntime.FailHook("Player.Interact", exception);
				return true;
			}
		}
	}
	internal enum MultiReplenishmentCatalogCommitCode
	{
		Commi