Decompiled source of The Employees Know v0.2.1

SmartStorage.dll

Decompiled 12 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text.Json;
using HarmonyLib;
using Il2CppFishNet;
using Il2CppFishNet.Managing;
using Il2CppFishNet.Object;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppScheduleOne.Core.Items.Framework;
using Il2CppScheduleOne.Delivery;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.Employees;
using Il2CppScheduleOne.EntityFramework;
using Il2CppScheduleOne.Growing;
using Il2CppScheduleOne.ItemFramework;
using Il2CppScheduleOne.Management;
using Il2CppScheduleOne.NPCs;
using Il2CppScheduleOne.NPCs.Behaviour;
using Il2CppScheduleOne.ObjectScripts;
using Il2CppScheduleOne.Persistence;
using Il2CppScheduleOne.Property;
using Il2CppScheduleOne.StationFramework;
using Il2CppScheduleOne.Storage;
using Il2CppScheduleOne.UI;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using SmartStorage;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Main), "The Employees Know", "0.2.1", "EmployeeOfTheMonth", null)]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("SmartStorage")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.1.0")]
[assembly: AssemblyInformationalVersion("0.2.1")]
[assembly: AssemblyProduct("SmartStorage")]
[assembly: AssemblyTitle("SmartStorage")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.2.1.0")]
[module: UnverifiableCode]
namespace SmartStorage;

internal static class BotanistDryableMover
{
	private static readonly Dictionary<IntPtr, TransitRoute> Routes = new Dictionary<IntPtr, TransitRoute>();

	internal static bool TryMove(Botanist botanist, MoveItemBehaviour move, StorageRef source, ItemSlot sourceSlot, QualityItemInstance item, DryingRack rack, int amount)
	{
		if ((Object)(object)rack == (Object)null)
		{
			return false;
		}
		ITransitEntity destination = ((Il2CppObjectBase)rack).TryCast<ITransitEntity>();
		return TryMoveTo(botanist, move, source, sourceSlot, (ItemInstance)(object)item, destination, "drying rack", amount);
	}

	internal static bool TryMoveTo(Botanist botanist, MoveItemBehaviour move, StorageRef source, ItemSlot sourceSlot, ItemInstance item, ITransitEntity destination, string label, int amount = 1)
	{
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Expected O, but got Unknown
		if ((Object)(object)botanist == (Object)null || (Object)(object)move == (Object)null || source == null || !source.IsValid || item == null || (Object)(object)item.Definition == (Object)null || destination == null)
		{
			return false;
		}
		try
		{
			if (((Behaviour)move).Active)
			{
				return false;
			}
		}
		catch
		{
			return false;
		}
		TransitRoute val = null;
		bool flag = false;
		try
		{
			val = new TransitRoute(source.Transit, destination);
			if (!move.CanGetToSource(val) || !move.CanGetToDestination(val))
			{
				return false;
			}
			string iD = ((BaseItemDefinition)item.Definition).ID;
			if (string.IsNullOrWhiteSpace(iD))
			{
				return false;
			}
			if (!move.IsTransitRouteValid(val, iD))
			{
				return false;
			}
			if (Routes.TryGetValue(((Il2CppObjectBase)botanist).Pointer, out var value) && value != null)
			{
				try
				{
					value.Destroy();
				}
				catch
				{
				}
				Routes.Remove(((Il2CppObjectBase)botanist).Pointer);
			}
			Routes[((Il2CppObjectBase)botanist).Pointer] = val;
			flag = true;
			if (amount < 1)
			{
				amount = 1;
			}
			move.Initialize(val, item.GetCopy(1), amount, false);
			((Behaviour)move).Enable();
			StorageIndex.Reserve(sourceSlot);
			Log.Debug("botanist " + SafeName(botanist) + " moving " + amount + "x " + iD + " from " + source.Name + " to " + label + ".");
			return true;
		}
		catch (Exception ex)
		{
			Log.Debug("botanist item move failed: " + ex.Message);
			return false;
		}
		finally
		{
			if (!flag && val != null)
			{
				try
				{
					val.Destroy();
				}
				catch
				{
				}
			}
		}
	}

	internal static void Reset()
	{
		foreach (TransitRoute value in Routes.Values)
		{
			if (value != null)
			{
				try
				{
					value.Destroy();
				}
				catch
				{
				}
			}
		}
		Routes.Clear();
	}

	private static string SafeName(Botanist botanist)
	{
		try
		{
			return ((Object)(object)botanist == (Object)null) ? "?" : (((NPC)botanist).FullName ?? "?");
		}
		catch
		{
			return "?";
		}
	}
}
internal static class BotanistScheduler
{
	private sealed class StorageCacheEntry
	{
		internal List<StorageRef> Storages;

		internal float Until;
	}

	internal static bool HookInstalled;

	private static readonly Dictionary<IntPtr, float> NextCheck = new Dictionary<IntPtr, float>();

	private static readonly HashSet<IntPtr> LoggedStartup = new HashSet<IntPtr>();

	private static readonly HashSet<IntPtr> Phase2Logged = new HashSet<IntPtr>();

	private static readonly Dictionary<IntPtr, StorageCacheEntry> StorageCache = new Dictionary<IntPtr, StorageCacheEntry>();

	private static readonly List<Botanist> CachedBotanists = new List<Botanist>();

	private static float NextScan;

	private static float NextBotanistScan;

	private static float LastHookTick;

	internal static void Reset()
	{
		NextCheck.Clear();
		LoggedStartup.Clear();
		Phase2Logged.Clear();
		StorageCache.Clear();
		CachedBotanists.Clear();
		NextScan = 0f;
		NextBotanistScan = 0f;
		LastHookTick = 0f;
	}

	internal static void OnEmployeeTick(Employee employee)
	{
		if (!Enabled())
		{
			return;
		}
		LastHookTick = Time.realtimeSinceStartup;
		try
		{
			Botanist val = (((Object)(object)employee == (Object)null) ? null : ((Il2CppObjectBase)employee).TryCast<Botanist>());
			if ((Object)(object)val != (Object)null)
			{
				ProcessBotanist(val);
			}
		}
		catch (Exception ex)
		{
			Log.Debug("botanist scheduler hook failed: " + ex.Message);
		}
	}

	internal static void Tick()
	{
		if (!Enabled())
		{
			return;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if ((!HookInstalled || !(realtimeSinceStartup - LastHookTick < 5f)) && !(realtimeSinceStartup < NextScan))
		{
			NextScan = realtimeSinceStartup + 0.5f;
			if (realtimeSinceStartup >= NextBotanistScan)
			{
				RefreshBotanists();
				NextBotanistScan = realtimeSinceStartup + 10f;
			}
			for (int i = 0; i < CachedBotanists.Count; i++)
			{
				ProcessBotanist(CachedBotanists[i]);
			}
		}
	}

	private static void RefreshBotanists()
	{
		CachedBotanists.Clear();
		HashSet<IntPtr> hashSet = new HashSet<IntPtr>();
		try
		{
			List<NPC> nPCRegistry = NPCManager.NPCRegistry;
			if (nPCRegistry != null)
			{
				for (int i = 0; i < nPCRegistry.Count; i++)
				{
					NPC val = nPCRegistry[i];
					if (!((Object)(object)val == (Object)null))
					{
						Botanist val2 = ((Il2CppObjectBase)val).TryCast<Botanist>();
						if ((Object)(object)val2 != (Object)null && hashSet.Add(((Il2CppObjectBase)val2).Pointer))
						{
							CachedBotanists.Add(val2);
						}
					}
				}
			}
		}
		catch (Exception ex)
		{
			Log.Debug("botanist registry scan failed: " + ex.Message);
		}
		if (CachedBotanists.Count > 0)
		{
			return;
		}
		try
		{
			List<Property> ownedProperties = Property.OwnedProperties;
			if (ownedProperties == null)
			{
				return;
			}
			for (int j = 0; j < ownedProperties.Count; j++)
			{
				Property val3 = ownedProperties[j];
				List<Employee> val4 = (((Object)(object)val3 == (Object)null) ? null : val3.Employees);
				if (val4 == null)
				{
					continue;
				}
				for (int k = 0; k < val4.Count; k++)
				{
					Employee val5 = val4[k];
					if (!((Object)(object)val5 == (Object)null))
					{
						Botanist val6 = ((Il2CppObjectBase)val5).TryCast<Botanist>();
						if ((Object)(object)val6 != (Object)null && hashSet.Add(((Il2CppObjectBase)val6).Pointer))
						{
							CachedBotanists.Add(val6);
						}
					}
				}
			}
		}
		catch (Exception ex2)
		{
			Log.Debug("botanist property scan failed: " + ex2.Message);
		}
	}

	private static void ProcessBotanist(Botanist botanist)
	{
		if (!Enabled() || (Object)(object)botanist == (Object)null)
		{
			return;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (NextCheck.TryGetValue(((Il2CppObjectBase)botanist).Pointer, out var value) && realtimeSinceStartup < value)
		{
			return;
		}
		NextCheck[((Il2CppObjectBase)botanist).Pointer] = realtimeSinceStartup + 0.5f;
		try
		{
			if (((Employee)botanist).Fired || !((Employee)botanist).CanWork())
			{
				return;
			}
			MoveItemBehaviour moveItemBehaviour = ((Employee)botanist).MoveItemBehaviour;
			if (((Object)(object)moveItemBehaviour != (Object)null && ((Behaviour)moveItemBehaviour).Active) || ((Employee)botanist).IsAnyWorkInProgress())
			{
				return;
			}
			BotanistConfiguration configuration = botanist.configuration;
			if (configuration == null)
			{
				return;
			}
			Property assignedProperty = ((Employee)botanist).AssignedProperty;
			if (!((Object)(object)assignedProperty == (Object)null))
			{
				if (LoggedStartup.Add(((Il2CppObjectBase)botanist).Pointer))
				{
					Log.Msg("[The Employees Know] botanist scheduler for " + SafeName(botanist) + ": " + Count<DryingRack>(configuration.AssignedRacks) + " rack(s), " + Count<MushroomSpawnStation>(configuration.AssignedSpawnStations) + " spawn station(s).");
				}
				if (!TryDryingRacks(botanist, configuration, assignedProperty))
				{
					TrySpawnStations(botanist, configuration, assignedProperty);
				}
			}
		}
		catch (Exception ex)
		{
			Log.Debug("botanist scheduler failed: " + ex.Message);
		}
	}

	private static bool TryDryingRacks(Botanist botanist, BotanistConfiguration config, Property property)
	{
		List<DryingRack> assignedRacks = config.AssignedRacks;
		if (assignedRacks == null || assignedRacks.Count == 0)
		{
			return false;
		}
		MoveItemBehaviour moveItemBehaviour = ((Employee)botanist).MoveItemBehaviour;
		if ((Object)(object)moveItemBehaviour == (Object)null)
		{
			return false;
		}
		List<StorageRef> list = null;
		for (int i = 0; i < assignedRacks.Count; i++)
		{
			DryingRack val = assignedRacks[i];
			if (!CanLoadRack(botanist, val))
			{
				continue;
			}
			if (list == null)
			{
				list = GetStorages(property);
				if (list == null || list.Count == 0)
				{
					return false;
				}
			}
			if (BotanistSupply.FindDryableForSlot(botanist, list, val.InputSlot, val.ItemCapacity, out var best, out var bestSlot, out var dryable, out var amount) && BotanistDryableMover.TryMove(botanist, moveItemBehaviour, best, bestSlot, dryable, val, amount))
			{
				LogPhase2(botanist, "load drying rack from " + best.Name + " (" + amount + " item(s)).");
				return true;
			}
		}
		return false;
	}

	private static bool TrySpawnStations(Botanist botanist, BotanistConfiguration config, Property property)
	{
		UseSpawnStationBehaviour useSpawnStationBehaviour = botanist._useSpawnStationBehaviour;
		if ((Object)(object)useSpawnStationBehaviour == (Object)null || !IsFree((Behaviour)(object)useSpawnStationBehaviour))
		{
			return false;
		}
		List<MushroomSpawnStation> assignedSpawnStations = config.AssignedSpawnStations;
		if (assignedSpawnStations == null || assignedSpawnStations.Count == 0)
		{
			return false;
		}
		for (int i = 0; i < assignedSpawnStations.Count; i++)
		{
			MushroomSpawnStation val = assignedSpawnStations[i];
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			bool flag;
			try
			{
				flag = useSpawnStationBehaviour.IsStationReady(val);
			}
			catch
			{
				continue;
			}
			if (!flag)
			{
				continue;
			}
			try
			{
				useSpawnStationBehaviour.AssignStation(val);
				if (!((Behaviour)useSpawnStationBehaviour).Active && !((Behaviour)useSpawnStationBehaviour).Started)
				{
					((Behaviour)useSpawnStationBehaviour).Enable();
				}
				LogPhase2(botanist, "use spawn station.");
				return true;
			}
			catch (Exception ex)
			{
				Log.Debug("botanist scheduler spawn station failed: " + ex.Message);
			}
		}
		MoveItemBehaviour moveItemBehaviour = ((Employee)botanist).MoveItemBehaviour;
		if ((Object)(object)moveItemBehaviour == (Object)null)
		{
			return false;
		}
		MushroomSpawnStation val2 = null;
		for (int j = 0; j < assignedSpawnStations.Count; j++)
		{
			if (NeedsSpawnFill(botanist, assignedSpawnStations[j]))
			{
				val2 = assignedSpawnStations[j];
				break;
			}
		}
		if ((Object)(object)val2 == (Object)null)
		{
			return false;
		}
		List<StorageRef> storages = GetStorages(property);
		if (storages == null || storages.Count == 0)
		{
			return false;
		}
		return TryFillSpawnStation(botanist, val2, storages, moveItemBehaviour);
	}

	private static bool NeedsSpawnFill(Botanist botanist, MushroomSpawnStation station)
	{
		if ((Object)(object)station == (Object)null)
		{
			return false;
		}
		try
		{
			if ((Object)(object)station.NPCUserObject != (Object)null)
			{
				NetworkObject networkObject = ((NetworkBehaviour)botanist).NetworkObject;
				if ((Object)(object)networkObject == (Object)null || ((Il2CppObjectBase)station.NPCUserObject).Pointer != ((Il2CppObjectBase)networkObject).Pointer)
				{
					return false;
				}
			}
			if (!station.DoesStationHaveOutputSpace())
			{
				return false;
			}
			if (station.GrainBagSlot != null && station.GrainBagSlot.ItemInstance == null && !station.GrainBagSlot.IsLocked && !station.GrainBagSlot.IsAddLocked)
			{
				return true;
			}
			if (station.SyringeSlot != null && station.SyringeSlot.ItemInstance == null && !station.SyringeSlot.IsLocked && !station.SyringeSlot.IsAddLocked)
			{
				return true;
			}
		}
		catch
		{
		}
		return false;
	}

	private static bool TryFillSpawnStation(Botanist botanist, MushroomSpawnStation station, List<StorageRef> storages, MoveItemBehaviour move)
	{
		try
		{
			if ((Object)(object)station.NPCUserObject != (Object)null)
			{
				NetworkObject networkObject = ((NetworkBehaviour)botanist).NetworkObject;
				if ((Object)(object)networkObject == (Object)null || ((Il2CppObjectBase)station.NPCUserObject).Pointer != ((Il2CppObjectBase)networkObject).Pointer)
				{
					return false;
				}
			}
			if (!station.DoesStationHaveOutputSpace())
			{
				return false;
			}
			ItemSlot val = null;
			if (station.GrainBagSlot != null && station.GrainBagSlot.ItemInstance == null)
			{
				val = station.GrainBagSlot;
			}
			else if (station.SyringeSlot != null && station.SyringeSlot.ItemInstance == null)
			{
				val = station.SyringeSlot;
			}
			if (val == null)
			{
				return false;
			}
			if (val.IsLocked || val.IsAddLocked)
			{
				return false;
			}
			ITransitEntity val2 = ((Il2CppObjectBase)station).TryCast<ITransitEntity>();
			if (val2 == null)
			{
				return false;
			}
			for (int i = 0; i < storages.Count; i++)
			{
				StorageRef storageRef = storages[i];
				if (!storageRef.IsValid || storageRef.OutputSlots == null || !BotanistSupply.CanReach(botanist, storageRef))
				{
					continue;
				}
				for (int j = 0; j < storageRef.OutputSlots.Count; j++)
				{
					ItemSlot val3 = storageRef.OutputSlots[j];
					if (val3 == null)
					{
						continue;
					}
					ItemInstance itemInstance;
					try
					{
						if (val3.ItemInstance != null && !val3.IsLocked && !val3.IsRemovalLocked)
						{
							itemInstance = val3.ItemInstance;
							if (!((Object)(object)itemInstance.Definition == (Object)null) && val.DoesItemMatchHardFilters(itemInstance))
							{
								goto IL_0156;
							}
						}
					}
					catch
					{
					}
					continue;
					IL_0156:
					if (BotanistDryableMover.TryMoveTo(botanist, move, storageRef, val3, itemInstance, val2, "spawn station"))
					{
						LogPhase2(botanist, "load spawn station from " + storageRef.Name + ".");
						return true;
					}
				}
			}
		}
		catch (Exception ex)
		{
			Log.Debug("botanist scheduler spawn station fill failed: " + ex.Message);
		}
		return false;
	}

	private static bool CanLoadRack(Botanist botanist, DryingRack rack)
	{
		if ((Object)(object)rack == (Object)null)
		{
			return false;
		}
		try
		{
			if (rack.IsOpen)
			{
				return false;
			}
			NetworkObject nPCUserObject = rack.NPCUserObject;
			if ((Object)(object)nPCUserObject != (Object)null)
			{
				NetworkObject networkObject = ((NetworkBehaviour)botanist).NetworkObject;
				if ((Object)(object)networkObject == (Object)null || ((Il2CppObjectBase)nPCUserObject).Pointer != ((Il2CppObjectBase)networkObject).Pointer)
				{
					return false;
				}
			}
			if (!rack.IsAcceptingItems)
			{
				return false;
			}
			if (rack.InputSlot == null)
			{
				return false;
			}
			if (rack.InputSlot.IsLocked || rack.InputSlot.IsAddLocked)
			{
				return false;
			}
			if (rack.InputSlot.IsAtCapacity)
			{
				return false;
			}
			if (rack.OutputSlot != null && rack.OutputSlot.IsAtCapacity)
			{
				return false;
			}
			if (rack.DryingOperations != null && rack.DryingOperations.Count > 0)
			{
				return false;
			}
			return true;
		}
		catch
		{
			return false;
		}
	}

	private static List<StorageRef> GetStorages(Property property)
	{
		if ((Object)(object)property == (Object)null)
		{
			return null;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (StorageCache.TryGetValue(((Il2CppObjectBase)property).Pointer, out var value) && realtimeSinceStartup < value.Until)
		{
			return value.Storages;
		}
		if (value == null)
		{
			value = new StorageCacheEntry();
			StorageCache[((Il2CppObjectBase)property).Pointer] = value;
		}
		if (value.Storages == null)
		{
			value.Storages = new List<StorageRef>();
		}
		StorageIndex.CollectForProperty(property, allowedOnly: true, value.Storages);
		value.Until = realtimeSinceStartup + 1f;
		return value.Storages;
	}

	private static bool IsFree(Behaviour behaviour)
	{
		try
		{
			return (Object)(object)behaviour != (Object)null && !behaviour.Active && !behaviour.Started;
		}
		catch
		{
			return false;
		}
	}

	private static int Count<T>(List<T> list)
	{
		try
		{
			return list?.Count ?? 0;
		}
		catch
		{
			return 0;
		}
	}

	private static void LogPhase2(Botanist botanist, string message)
	{
		if (Phase2Logged.Add(((Il2CppObjectBase)botanist).Pointer))
		{
			Log.Msg("[The Employees Know] botanist " + SafeName(botanist) + ": " + message);
		}
		else
		{
			Log.Debug("botanist " + SafeName(botanist) + ": " + message);
		}
	}

	private static string SafeName(Botanist botanist)
	{
		try
		{
			return ((Object)(object)botanist == (Object)null) ? "?" : (((NPC)botanist).FullName ?? "?");
		}
		catch
		{
			return "?";
		}
	}

	private static bool Enabled()
	{
		if (!Net.IsServer)
		{
			return false;
		}
		if (Config.Enabled != null && Config.Enabled.Value && Config.EnableBotanist != null)
		{
			return Config.EnableBotanist.Value;
		}
		return false;
	}
}
internal static class BotanistSupply
{
	private sealed class Chosen
	{
		internal StorageRef Storage;

		internal float Until;

		internal string Kind;
	}

	private sealed class SupplyEntry
	{
		internal bool Found;

		internal StorageRef Storage;

		internal ItemSlot Slot;

		internal float Until;
	}

	private sealed class AnyEntry
	{
		internal bool Found;

		internal StorageRef Storage;

		internal float Until;
	}

	private sealed class DryableEntry
	{
		internal bool Found;

		internal StorageRef Storage;

		internal ItemSlot Slot;

		internal QualityItemInstance Dryable;

		internal float Until;
	}

	private sealed class StorageListEntry
	{
		internal List<StorageRef> Storages;

		internal float Until;
	}

	private static readonly Dictionary<IntPtr, Chosen> ChosenByBotanist = new Dictionary<IntPtr, Chosen>();

	private static readonly Dictionary<(IntPtr Botanist, IntPtr Storage), (bool Value, float Until)> Reachability = new Dictionary<(IntPtr, IntPtr), (bool, float)>();

	private static readonly Dictionary<(IntPtr Botanist, ulong Hash, int Count), SupplyEntry> SupplyCache = new Dictionary<(IntPtr, ulong, int), SupplyEntry>();

	private static readonly Dictionary<IntPtr, AnyEntry> AnyStorageCache = new Dictionary<IntPtr, AnyEntry>();

	private static readonly Dictionary<IntPtr, DryableEntry> DryableCache = new Dictionary<IntPtr, DryableEntry>();

	private static readonly Dictionary<IntPtr, StorageListEntry> StorageCache = new Dictionary<IntPtr, StorageListEntry>();

	private static readonly Dictionary<(IntPtr Behaviour, IntPtr Container), (string[] Ids, float Until)> IdsCache = new Dictionary<(IntPtr, IntPtr), (string[], float)>();

	private static readonly Dictionary<IntPtr, Botanist> BotanistCache = new Dictionary<IntPtr, Botanist>();

	private static readonly Dictionary<(IntPtr Botanist, ulong Hash), float> LogUntil = new Dictionary<(IntPtr, ulong), float>();

	internal static void Reset()
	{
		ChosenByBotanist.Clear();
		Reachability.Clear();
		SupplyCache.Clear();
		AnyStorageCache.Clear();
		DryableCache.Clear();
		StorageCache.Clear();
		IdsCache.Clear();
		BotanistCache.Clear();
		LogUntil.Clear();
	}

	private static List<StorageRef> GetStorages(Property property)
	{
		if ((Object)(object)property == (Object)null)
		{
			return null;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (StorageCache.TryGetValue(((Il2CppObjectBase)property).Pointer, out var value) && realtimeSinceStartup < value.Until)
		{
			return value.Storages;
		}
		if (value == null)
		{
			value = new StorageListEntry();
			StorageCache[((Il2CppObjectBase)property).Pointer] = value;
		}
		if (value.Storages == null)
		{
			value.Storages = new List<StorageRef>();
		}
		StorageIndex.CollectForProperty(property, allowedOnly: true, value.Storages);
		value.Until = realtimeSinceStartup + 1f;
		return value.Storages;
	}

	private static ulong HashIds(string[] ids)
	{
		ulong num = 1469598103934665603uL;
		foreach (string text in ids)
		{
			if (text != null)
			{
				for (int j = 0; j < text.Length; j++)
				{
					num ^= text[j];
					num *= 1099511628211L;
				}
			}
			num ^= 0xFF;
			num *= 1099511628211L;
		}
		return num;
	}

	private static bool ShouldLog(Botanist botanist, ulong hash, float interval)
	{
		if ((Object)(object)botanist == (Object)null || !Log.DebugEnabled)
		{
			return false;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		(IntPtr, ulong) key = (((Il2CppObjectBase)botanist).Pointer, hash);
		if (LogUntil.TryGetValue(key, out var value) && realtimeSinceStartup < value)
		{
			return false;
		}
		LogUntil[key] = realtimeSinceStartup + interval;
		return true;
	}

	private static bool IsUsable(StorageRef storage, ItemSlot slot)
	{
		if (storage == null || !storage.IsValid || slot == null || slot.ItemInstance == null)
		{
			return false;
		}
		try
		{
			return !slot.IsLocked && !slot.IsRemovalLocked;
		}
		catch
		{
			return false;
		}
	}

	internal static bool CanReach(Botanist botanist, StorageRef storage)
	{
		if ((Object)(object)botanist == (Object)null || storage == null || !storage.IsValid)
		{
			return false;
		}
		if (storage.Transit == null)
		{
			return false;
		}
		(IntPtr, IntPtr) key = (((Il2CppObjectBase)botanist).Pointer, ((Il2CppObjectBase)storage.Transit).Pointer);
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (Reachability.TryGetValue(key, out (bool, float) value) && realtimeSinceStartup < value.Item2)
		{
			return value.Item1;
		}
		bool flag;
		try
		{
			flag = ((NPC)botanist).Movement.CanGetTo(storage.Transit, 1f);
		}
		catch
		{
			flag = true;
		}
		Reachability[key] = (flag, realtimeSinceStartup + 5f);
		return flag;
	}

	internal static void GetDryableInSuppliesPrefix(Botanist __instance)
	{
		if (!((Object)(object)__instance == (Object)null) && Enabled())
		{
			ChosenByBotanist.Remove(((Il2CppObjectBase)__instance).Pointer);
		}
	}

	internal static void GetDryableInSuppliesPostfix(Botanist __instance, ref QualityItemInstance __result)
	{
		if ((Object)(object)__instance == (Object)null || !Enabled())
		{
			return;
		}
		if (__result != null)
		{
			if (!SupplyLockerBlocked(__instance))
			{
				return;
			}
			__result = null;
		}
		try
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			IntPtr pointer = ((Il2CppObjectBase)__instance).Pointer;
			if (DryableCache.TryGetValue(pointer, out var value) && realtimeSinceStartup < value.Until)
			{
				if (!value.Found)
				{
					return;
				}
				if (IsUsable(value.Storage, value.Slot))
				{
					ChosenByBotanist[pointer] = new Chosen
					{
						Storage = value.Storage,
						Until = realtimeSinceStartup + 60f,
						Kind = "dryable"
					};
					__result = value.Dryable;
					return;
				}
				DryableCache.Remove(pointer);
			}
			Property assignedProperty = ((Employee)__instance).AssignedProperty;
			if ((Object)(object)assignedProperty == (Object)null)
			{
				return;
			}
			List<StorageRef> storages = GetStorages(assignedProperty);
			if (storages == null)
			{
				return;
			}
			StorageRef best;
			ItemSlot bestSlot;
			QualityItemInstance dryable;
			bool flag = FindDryable(__instance, storages, out best, out bestSlot, out dryable);
			DryableCache[pointer] = new DryableEntry
			{
				Found = flag,
				Storage = best,
				Slot = bestSlot,
				Dryable = dryable,
				Until = realtimeSinceStartup + 1f
			};
			if (!flag)
			{
				ChosenByBotanist.Remove(pointer);
				return;
			}
			ChosenByBotanist[pointer] = new Chosen
			{
				Storage = best,
				Until = realtimeSinceStartup + 60f,
				Kind = "dryable"
			};
			__result = dryable;
			if (ShouldLog(__instance, 11400714819323198485uL, 5f))
			{
				Log.Debug("botanist " + SafeName(__instance) + " dryable supply: " + best.Name);
			}
		}
		catch (Exception ex)
		{
			Log.Debug("botanist dryable supply postfix failed: " + ex.Message);
		}
	}

	private static bool SupplyLockerBlocked(Botanist botanist)
	{
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			BotanistConfiguration configuration = botanist.configuration;
			BuildableItem val = ((configuration == null || configuration.Supplies == null) ? null : configuration.Supplies.SelectedObject);
			PlaceableStorageEntity val2 = (((Object)(object)val == (Object)null) ? null : ((Il2CppObjectBase)val).TryCast<PlaceableStorageEntity>());
			if ((Object)(object)val2 == (Object)null)
			{
				return false;
			}
			return !StoragePermissions.IsAllowed(((object)new ITransitEntity(((Il2CppObjectBase)val2).Pointer).GUID/*cast due to .constrained prefix*/).ToString());
		}
		catch
		{
			return false;
		}
	}

	internal static bool FindDryable(Botanist botanist, List<StorageRef> storages, out StorageRef best, out ItemSlot bestSlot, out QualityItemInstance dryable)
	{
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
		best = null;
		bestSlot = null;
		dryable = null;
		int num = int.MaxValue;
		float num2 = float.MaxValue;
		for (int i = 0; i < storages.Count; i++)
		{
			StorageRef storageRef = storages[i];
			if (!storageRef.IsValid || storageRef.OutputSlots == null)
			{
				continue;
			}
			for (int j = 0; j < storageRef.OutputSlots.Count; j++)
			{
				ItemSlot val = storageRef.OutputSlots[j];
				if (val == null)
				{
					continue;
				}
				try
				{
					if (val.IsLocked || val.IsRemovalLocked)
					{
						continue;
					}
					goto IL_0079;
				}
				catch
				{
				}
				continue;
				IL_0079:
				ItemInstance itemInstance = val.ItemInstance;
				if (itemInstance == null)
				{
					continue;
				}
				bool flag;
				try
				{
					flag = ItemFilter_Dryable.IsItemDryable(itemInstance);
				}
				catch
				{
					continue;
				}
				if (!flag)
				{
					continue;
				}
				QualityItemInstance val2 = ((Il2CppObjectBase)itemInstance).TryCast<QualityItemInstance>();
				if (val2 != null && CanReach(botanist, storageRef))
				{
					Vector3 val3 = ((Component)botanist).transform.position - storageRef.Position;
					float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude;
					int priority = storageRef.Priority;
					if (best == null || priority < num || (priority == num && sqrMagnitude < num2))
					{
						best = storageRef;
						bestSlot = val;
						dryable = val2;
						num = priority;
						num2 = sqrMagnitude;
					}
				}
			}
		}
		return best != null;
	}

	internal static bool FindDryableForSlot(Botanist botanist, List<StorageRef> storages, ItemSlot destination, int fallbackCapacity, out StorageRef best, out ItemSlot bestSlot, out QualityItemInstance dryable, out int amount)
	{
		//IL_019d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
		best = null;
		bestSlot = null;
		dryable = null;
		amount = 0;
		if (destination == null)
		{
			return false;
		}
		int num = int.MaxValue;
		float num2 = float.MaxValue;
		for (int i = 0; i < storages.Count; i++)
		{
			StorageRef storageRef = storages[i];
			if (!storageRef.IsValid || storageRef.OutputSlots == null)
			{
				continue;
			}
			for (int j = 0; j < storageRef.OutputSlots.Count; j++)
			{
				ItemSlot val = storageRef.OutputSlots[j];
				if (val == null)
				{
					continue;
				}
				try
				{
					if (val.IsLocked || val.IsRemovalLocked || val.ItemInstance == null || val.Quantity < 1)
					{
						continue;
					}
					goto IL_009c;
				}
				catch
				{
				}
				continue;
				IL_009c:
				ItemInstance itemInstance = val.ItemInstance;
				if (itemInstance == null || (Object)(object)itemInstance.Definition == (Object)null)
				{
					continue;
				}
				bool flag;
				try
				{
					flag = ItemFilter_Dryable.IsItemDryable(itemInstance);
				}
				catch
				{
					continue;
				}
				if (!flag)
				{
					continue;
				}
				QualityItemInstance val2 = ((Il2CppObjectBase)itemInstance).TryCast<QualityItemInstance>();
				if (val2 == null)
				{
					continue;
				}
				try
				{
					if (destination.ItemInstance == null)
					{
						goto IL_012b;
					}
					ItemInstance itemInstance2 = destination.ItemInstance;
					if (!((Object)(object)itemInstance2.Definition == (Object)null) && !(((BaseItemDefinition)itemInstance2.Definition).ID != ((BaseItemDefinition)itemInstance.Definition).ID))
					{
						goto IL_012b;
					}
					goto end_IL_00e8;
					IL_012b:
					if (!destination.DoesItemMatchHardFilters(itemInstance))
					{
						continue;
					}
					int num3 = ((destination.ItemInstance != null) ? destination.Quantity : 0);
					int num4 = destination.GetCapacityForItem(itemInstance, false);
					if (num4 <= 0)
					{
						num4 = fallbackCapacity;
					}
					if (num4 <= num3)
					{
						continue;
					}
					int num5 = num4 - num3;
					int num6 = val.Quantity;
					if (num6 > num5)
					{
						num6 = num5;
					}
					if (num6 >= 1 && CanReach(botanist, storageRef))
					{
						Vector3 val3 = ((Component)botanist).transform.position - storageRef.Position;
						float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude;
						int priority = storageRef.Priority;
						if (best == null || priority < num || (priority == num && sqrMagnitude < num2))
						{
							best = storageRef;
							bestSlot = val;
							dryable = val2;
							amount = num6;
							num = priority;
							num2 = sqrMagnitude;
						}
					}
					end_IL_00e8:;
				}
				catch
				{
				}
			}
		}
		return best != null;
	}

	internal static bool DoesBotanistHaveAccessPrefix(GrowContainerBehaviour __instance, GrowContainer __0, ref bool __result)
	{
		if (!Enabled())
		{
			return true;
		}
		try
		{
			if (!ChooseForContainer(__instance, __0, out var _))
			{
				return true;
			}
			__result = true;
			return false;
		}
		catch (Exception ex)
		{
			Log.Debug("DoesBotanistHaveAccess prefix failed: " + ex.Message);
			return true;
		}
	}

	internal static bool DoSuppliesContainPrefix(GrowContainerBehaviour __instance, GrowContainer __0, ref bool __result)
	{
		if (!Enabled())
		{
			return true;
		}
		try
		{
			if (!ChooseForContainer(__instance, __0, out var _))
			{
				return true;
			}
			__result = true;
			return false;
		}
		catch (Exception ex)
		{
			Log.Debug("DoSuppliesContain prefix failed: " + ex.Message);
			return true;
		}
	}

	internal static bool GetSuppliesSlotPrefix(GrowContainerBehaviour __instance, Il2CppStringArray __0, ref ItemSlot __result)
	{
		string[] array = ToArray(__0);
		if (array == null || array.Length == 0)
		{
			return true;
		}
		if (!Enabled())
		{
			return true;
		}
		try
		{
			if (!ChooseForIds(__instance, array, out var slot))
			{
				return true;
			}
			__result = slot;
			return false;
		}
		catch (Exception ex)
		{
			Log.Debug("botanist supply slot prefix failed: " + ex.Message);
			return true;
		}
	}

	internal static void GetSuppliesAsTransitEntityPostfix(Botanist __instance, ref ITransitEntity __result)
	{
		if (!Enabled() || (Object)(object)__instance == (Object)null)
		{
			return;
		}
		try
		{
			if (__result != null && SupplyLockerBlocked(__instance))
			{
				__result = null;
				ChosenByBotanist.Remove(((Il2CppObjectBase)__instance).Pointer);
			}
			StorageRef chosen2;
			if (TryGetChosen(__instance, out var chosen))
			{
				__result = chosen.Storage.Transit;
			}
			else if (__result == null && ChooseAnySupplyStorage(__instance, out chosen2))
			{
				__result = chosen2.Transit;
				Log.Debug("botanist " + SafeName(__instance) + " fallback supply storage: " + chosen2.Name);
			}
		}
		catch (Exception ex)
		{
			Log.Debug("botanist supply transit postfix failed: " + ex.Message);
		}
	}

	private static bool TryGetChosen(Botanist botanist, out Chosen chosen)
	{
		chosen = null;
		if ((Object)(object)botanist == (Object)null)
		{
			return false;
		}
		if (!ChosenByBotanist.TryGetValue(((Il2CppObjectBase)botanist).Pointer, out chosen) || chosen == null)
		{
			ChosenByBotanist.Remove(((Il2CppObjectBase)botanist).Pointer);
			return false;
		}
		if (Time.realtimeSinceStartup > chosen.Until || chosen.Storage == null || !chosen.Storage.IsValid)
		{
			ChosenByBotanist.Remove(((Il2CppObjectBase)botanist).Pointer);
			chosen = null;
			return false;
		}
		return true;
	}

	private static bool ChooseAnySupplyStorage(Botanist botanist, out StorageRef chosen)
	{
		//IL_0136: Unknown result type (might be due to invalid IL or missing references)
		//IL_013d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0142: Unknown result type (might be due to invalid IL or missing references)
		//IL_0147: Unknown result type (might be due to invalid IL or missing references)
		chosen = null;
		if ((Object)(object)botanist == (Object)null)
		{
			return false;
		}
		try
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (AnyStorageCache.TryGetValue(((Il2CppObjectBase)botanist).Pointer, out var value) && realtimeSinceStartup < value.Until)
			{
				if (!value.Found)
				{
					return false;
				}
				if (value.Storage != null && value.Storage.IsValid)
				{
					chosen = value.Storage;
					ChosenByBotanist[((Il2CppObjectBase)botanist).Pointer] = new Chosen
					{
						Storage = chosen,
						Until = realtimeSinceStartup + 5f,
						Kind = "fallback"
					};
					return true;
				}
				AnyStorageCache.Remove(((Il2CppObjectBase)botanist).Pointer);
			}
			Property assignedProperty = ((Employee)botanist).AssignedProperty;
			if ((Object)(object)assignedProperty == (Object)null)
			{
				return false;
			}
			List<StorageRef> storages = GetStorages(assignedProperty);
			if (storages == null)
			{
				return false;
			}
			StorageRef storageRef = null;
			int num = int.MaxValue;
			float num2 = float.MaxValue;
			for (int i = 0; i < storages.Count; i++)
			{
				StorageRef storageRef2 = storages[i];
				if (storageRef2.IsValid && storageRef2.OutputSlots != null && storageRef2.OutputSlots.Count != 0 && HasAnyItem(storageRef2) && CanReach(botanist, storageRef2))
				{
					Vector3 val = ((Component)botanist).transform.position - storageRef2.Position;
					float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
					int priority = storageRef2.Priority;
					if (storageRef == null || priority < num || (priority == num && sqrMagnitude < num2))
					{
						storageRef = storageRef2;
						num = priority;
						num2 = sqrMagnitude;
					}
				}
			}
			AnyStorageCache[((Il2CppObjectBase)botanist).Pointer] = new AnyEntry
			{
				Found = (storageRef != null),
				Storage = storageRef,
				Until = realtimeSinceStartup + 1f
			};
			if (storageRef == null)
			{
				return false;
			}
			chosen = storageRef;
			ChosenByBotanist[((Il2CppObjectBase)botanist).Pointer] = new Chosen
			{
				Storage = storageRef,
				Until = realtimeSinceStartup + 5f,
				Kind = "fallback"
			};
			return true;
		}
		catch (Exception ex)
		{
			Log.Debug("ChooseAnySupplyStorage failed: " + ex.Message);
			return false;
		}
	}

	private static bool HasAnyItem(StorageRef storage)
	{
		List<ItemSlot> outputSlots = storage.OutputSlots;
		if (outputSlots == null)
		{
			return false;
		}
		for (int i = 0; i < outputSlots.Count; i++)
		{
			try
			{
				ItemSlot val = outputSlots[i];
				if (val != null && val.ItemInstance != null)
				{
					return true;
				}
			}
			catch
			{
			}
		}
		return false;
	}

	private static bool ChooseForContainer(GrowContainerBehaviour behaviour, GrowContainer container, out ItemSlot slot)
	{
		slot = null;
		if (!Enabled() || (Object)(object)behaviour == (Object)null || (Object)(object)container == (Object)null)
		{
			return false;
		}
		try
		{
			Botanist botanist = GetBotanist(behaviour);
			if ((Object)(object)botanist == (Object)null)
			{
				return false;
			}
			string[] requiredItemSuitableIDs = GetRequiredItemSuitableIDs(behaviour, container);
			if (requiredItemSuitableIDs == null || requiredItemSuitableIDs.Length == 0)
			{
				ChosenByBotanist.Remove(((Il2CppObjectBase)botanist).Pointer);
				return false;
			}
			return ResolveSupply(botanist, requiredItemSuitableIDs, out slot);
		}
		catch (Exception ex)
		{
			Log.Debug("ChooseForContainer failed: " + ex.Message);
			return false;
		}
	}

	private static bool ChooseForIds(GrowContainerBehaviour behaviour, string[] ids, out ItemSlot slot)
	{
		slot = null;
		if (!Enabled() || (Object)(object)behaviour == (Object)null || ids == null || ids.Length == 0)
		{
			return false;
		}
		try
		{
			Botanist botanist = GetBotanist(behaviour);
			if ((Object)(object)botanist == (Object)null)
			{
				return false;
			}
			return ResolveSupply(botanist, ids, out slot);
		}
		catch (Exception ex)
		{
			Log.Debug("ChooseForIds failed: " + ex.Message);
			return false;
		}
	}

	private static bool ResolveSupply(Botanist botanist, string[] ids, out ItemSlot slot)
	{
		slot = null;
		if ((Object)(object)botanist == (Object)null || ids == null || ids.Length == 0)
		{
			return false;
		}
		try
		{
			ulong num = HashIds(ids);
			(IntPtr, ulong, int) key = (((Il2CppObjectBase)botanist).Pointer, num, ids.Length);
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (SupplyCache.TryGetValue(key, out var value) && realtimeSinceStartup < value.Until)
			{
				if (!value.Found)
				{
					return false;
				}
				if (IsUsable(value.Storage, value.Slot))
				{
					slot = value.Slot;
					ChosenByBotanist[((Il2CppObjectBase)botanist).Pointer] = new Chosen
					{
						Storage = value.Storage,
						Until = realtimeSinceStartup + 60f,
						Kind = "container"
					};
					return true;
				}
				SupplyCache.Remove(key);
			}
			Property assignedProperty = ((Employee)botanist).AssignedProperty;
			if ((Object)(object)assignedProperty == (Object)null)
			{
				ChosenByBotanist.Remove(((Il2CppObjectBase)botanist).Pointer);
				return false;
			}
			List<StorageRef> storages = GetStorages(assignedProperty);
			if (storages == null)
			{
				ChosenByBotanist.Remove(((Il2CppObjectBase)botanist).Pointer);
				return false;
			}
			StorageRef chosenStorage;
			bool flag = StorageIndex.FindBestForBotanist(botanist, ids, storages, CanReach, out chosenStorage, out slot);
			SupplyCache[key] = new SupplyEntry
			{
				Found = flag,
				Storage = chosenStorage,
				Slot = slot,
				Until = realtimeSinceStartup + 1f
			};
			if (!flag)
			{
				ChosenByBotanist.Remove(((Il2CppObjectBase)botanist).Pointer);
				if (ShouldLog(botanist, num, 5f))
				{
					Log.Debug("botanist " + SafeName(botanist) + " found no supply storage for " + string.Join(",", ids) + " (" + storages.Count + " enabled storage(s)).");
				}
				return false;
			}
			ChosenByBotanist[((Il2CppObjectBase)botanist).Pointer] = new Chosen
			{
				Storage = chosenStorage,
				Until = realtimeSinceStartup + 60f,
				Kind = "container"
			};
			if (ShouldLog(botanist, num, 5f))
			{
				Log.Debug("botanist " + SafeName(botanist) + " will use " + chosenStorage.Name + " for " + string.Join(",", ids));
			}
			return true;
		}
		catch (Exception ex)
		{
			Log.Debug("ResolveSupply failed: " + ex.Message);
			return false;
		}
	}

	private static bool Enabled()
	{
		if (!Net.IsServer)
		{
			return false;
		}
		if (Config.Enabled != null && Config.Enabled.Value && Config.EnableBotanist != null)
		{
			return Config.EnableBotanist.Value;
		}
		return false;
	}

	private static Botanist GetBotanist(GrowContainerBehaviour behaviour)
	{
		if ((Object)(object)behaviour == (Object)null)
		{
			return null;
		}
		if (BotanistCache.TryGetValue(((Il2CppObjectBase)behaviour).Pointer, out var value) && (Object)(object)value != (Object)null)
		{
			return value;
		}
		try
		{
			Botanist value2 = Traverse.Create((object)behaviour).Property("_botanist", (object[])null).GetValue<Botanist>();
			if ((Object)(object)value2 != (Object)null)
			{
				BotanistCache[((Il2CppObjectBase)behaviour).Pointer] = value2;
			}
			return value2;
		}
		catch
		{
			try
			{
				PropertyInfo propertyInfo = AccessTools.Property(typeof(GrowContainerBehaviour), "_botanist");
				Botanist val = (Botanist)((propertyInfo == null) ? null : /*isinst with value type is only supported in some contexts*/);
				if ((Object)(object)val != (Object)null)
				{
					BotanistCache[((Il2CppObjectBase)behaviour).Pointer] = val;
				}
				return val;
			}
			catch
			{
				return null;
			}
		}
	}

	internal static void SetChosen(Botanist botanist, StorageRef storage, string kind)
	{
		if (!((Object)(object)botanist == (Object)null) && storage != null)
		{
			ChosenByBotanist[((Il2CppObjectBase)botanist).Pointer] = new Chosen
			{
				Storage = storage,
				Until = Time.realtimeSinceStartup + 60f,
				Kind = kind
			};
		}
	}

	internal static void ClearChosen(Botanist botanist)
	{
		if (!((Object)(object)botanist == (Object)null))
		{
			ChosenByBotanist.Remove(((Il2CppObjectBase)botanist).Pointer);
		}
	}

	internal static string[] GetRequiredItemSuitableIDs(GrowContainerBehaviour behaviour, GrowContainer container)
	{
		if ((Object)(object)behaviour == (Object)null || (Object)(object)container == (Object)null)
		{
			return null;
		}
		(IntPtr, IntPtr) key = (((Il2CppObjectBase)behaviour).Pointer, ((Il2CppObjectBase)container).Pointer);
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (IdsCache.TryGetValue(key, out (string[], float) value) && realtimeSinceStartup < value.Item2)
		{
			return value.Item1;
		}
		try
		{
			object[] parameters = new object[1] { container };
			MethodInfo methodInfo = AccessTools.Method(((object)behaviour).GetType(), "GetRequiredItemSuitableIDs", new Type[1] { typeof(GrowContainer) }, (Type[])null);
			if (methodInfo == null)
			{
				methodInfo = AccessTools.Method(typeof(GrowContainerBehaviour), "GetRequiredItemSuitableIDs", new Type[1] { typeof(GrowContainer) }, (Type[])null);
			}
			if (methodInfo == null)
			{
				return null;
			}
			object obj = methodInfo.Invoke(behaviour, parameters);
			Il2CppStringArray val = (Il2CppStringArray)((obj is Il2CppStringArray) ? obj : null);
			string[] array = ((val != null) ? ToArray(val) : (obj as string[]));
			if (array != null)
			{
				IdsCache[key] = (array, realtimeSinceStartup + 2f);
			}
			return array;
		}
		catch (Exception ex)
		{
			Log.Debug("GetRequiredItemSuitableIDs failed: " + ex.Message);
			return null;
		}
	}

	private static string[] ToArray(Il2CppStringArray array)
	{
		if (array == null)
		{
			return null;
		}
		string[] array2 = new string[((Il2CppArrayBase<string>)(object)array).Length];
		for (int i = 0; i < ((Il2CppArrayBase<string>)(object)array).Length; i++)
		{
			array2[i] = ((Il2CppArrayBase<string>)(object)array)[i];
		}
		return array2;
	}

	private static string SafeName(Botanist botanist)
	{
		try
		{
			return ((Object)(object)botanist == (Object)null) ? "?" : (((NPC)botanist).FullName ?? "?");
		}
		catch
		{
			return "?";
		}
	}
}
internal static class ChainMover
{
	private sealed class Watch
	{
		internal float Since;
	}

	private sealed class RouteHolder
	{
		internal TransitRoute Route;

		internal bool Owned;
	}

	private sealed class SourceInfo
	{
		internal BuildableItem Station;

		internal ItemSlot Output;

		internal ITransitEntity Entity;

		internal ITransitEntity Destination;

		internal TransitRoute Route;
	}

	private static readonly Dictionary<IntPtr, Watch> Watches = new Dictionary<IntPtr, Watch>();

	private static readonly Dictionary<IntPtr, RouteHolder> Routes = new Dictionary<IntPtr, RouteHolder>();

	private static readonly Dictionary<IntPtr, float> Reserved = new Dictionary<IntPtr, float>();

	internal static bool TryMove(Chemist chemist, MoveItemBehaviour move, ChemistConfiguration config, Property property)
	{
		//IL_023b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0242: Expected O, but got Unknown
		if ((Object)(object)chemist == (Object)null || (Object)(object)move == (Object)null || config == null)
		{
			return false;
		}
		if (Config.EnableChainMover == null || !Config.EnableChainMover.Value)
		{
			return false;
		}
		try
		{
			if (((Behaviour)move).Active)
			{
				return false;
			}
		}
		catch
		{
			return false;
		}
		List<BuildableItem> list = new List<BuildableItem>();
		AddSources<MixingStation>(list, config.MixStations);
		AddSources<ChemistryStation>(list, config.ChemStations);
		AddSources<Cauldron>(list, config.Cauldrons);
		AddSources<LabOven>(list, config.LabOvens);
		if (list.Count == 0)
		{
			return false;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		Cleanup(realtimeSinceStartup);
		for (int i = 0; i < list.Count; i++)
		{
			if (!TryGetSource(list[i], out var info))
			{
				continue;
			}
			ItemSlot output = info.Output;
			if (output == null)
			{
				Watches.Remove(((Il2CppObjectBase)info.Station).Pointer);
				continue;
			}
			ItemInstance itemInstance = output.ItemInstance;
			if (itemInstance == null || (Object)(object)itemInstance.Definition == (Object)null)
			{
				Watches.Remove(((Il2CppObjectBase)info.Station).Pointer);
				continue;
			}
			try
			{
				if (output.IsLocked || output.IsRemovalLocked)
				{
					continue;
				}
				goto IL_012d;
			}
			catch
			{
			}
			continue;
			IL_012d:
			if (Reserved.TryGetValue(((Il2CppObjectBase)info.Station).Pointer, out var value) && realtimeSinceStartup < value)
			{
				continue;
			}
			int num;
			try
			{
				num = info.Destination.GetInputCapacityForItem(itemInstance, (NPC)(object)chemist, false);
			}
			catch
			{
				num = 0;
			}
			if (num <= 0)
			{
				Watches.Remove(((Il2CppObjectBase)info.Station).Pointer);
				continue;
			}
			if (!Watches.TryGetValue(((Il2CppObjectBase)info.Station).Pointer, out var value2))
			{
				Watches[((Il2CppObjectBase)info.Station).Pointer] = new Watch
				{
					Since = realtimeSinceStartup
				};
				continue;
			}
			float num2 = ((Config.ChainMoveDelaySeconds == null) ? 3f : Config.ChainMoveDelaySeconds.Value);
			if (num2 < 0.5f)
			{
				num2 = 0.5f;
			}
			if (realtimeSinceStartup - value2.Since < num2)
			{
				continue;
			}
			TransitRoute val = info.Route;
			bool flag = false;
			if (val == null || !SameEndpoints(val, info.Entity, info.Destination))
			{
				val = new TransitRoute(info.Entity, info.Destination);
				flag = true;
			}
			bool flag2;
			try
			{
				flag2 = move.CanGetToSource(val) && move.CanGetToDestination(val);
			}
			catch
			{
				flag2 = false;
			}
			if (!flag2)
			{
				if (flag)
				{
					SafeDestroy(val);
				}
				continue;
			}
			int num3 = num;
			if (num3 < 1)
			{
				num3 = 1;
			}
			StoreRoute(((Il2CppObjectBase)info.Station).Pointer, val, flag);
			try
			{
				move.Initialize(val, itemInstance.GetCopy(1), num3, false);
				((Behaviour)move).Enable();
				Reserved[((Il2CppObjectBase)info.Station).Pointer] = realtimeSinceStartup + 5f;
				Watches.Remove(((Il2CppObjectBase)info.Station).Pointer);
				Log.Debug("chain move: " + num3 + "x " + ((BaseItemDefinition)itemInstance.Definition).ID + " from " + SafeName(info.Station) + " to " + SafeName(info.Destination));
				return true;
			}
			catch (Exception ex)
			{
				Log.Warning("[The Employees Know] Chain move failed: " + ex.Message);
				if (flag)
				{
					SafeDestroy(val);
					Routes.Remove(((Il2CppObjectBase)info.Station).Pointer);
				}
				return false;
			}
		}
		return false;
	}

	internal static void Reset()
	{
		foreach (RouteHolder value in Routes.Values)
		{
			if (value != null && value.Owned)
			{
				SafeDestroy(value.Route);
			}
		}
		Routes.Clear();
		Watches.Clear();
		Reserved.Clear();
	}

	private static void AddSources<T>(List<BuildableItem> list, List<T> stations) where T : BuildableItem
	{
		if (stations == null)
		{
			return;
		}
		for (int i = 0; i < stations.Count; i++)
		{
			T val = stations[i];
			if ((Object)(object)val != (Object)null)
			{
				list.Add((BuildableItem)(object)val);
			}
		}
	}

	private static bool TryGetSource(BuildableItem station, out SourceInfo info)
	{
		info = null;
		if ((Object)(object)station == (Object)null)
		{
			return false;
		}
		ITransitEntity val = ((Il2CppObjectBase)station).TryCast<ITransitEntity>();
		if (val == null)
		{
			return false;
		}
		ItemSlot val2 = null;
		ITransitEntity val3 = null;
		TransitRoute route = null;
		MixingStation val4 = ((Il2CppObjectBase)station).TryCast<MixingStation>();
		if ((Object)(object)val4 != (Object)null)
		{
			val2 = val4.OutputSlot;
			MixingStationConfiguration val5 = MixerChain.ConfigOf(val4);
			if (val5 != null)
			{
				val3 = DestinationOf(val5.Destination);
				route = val5.DestinationRoute;
			}
		}
		else
		{
			ChemistryStation val6 = ((Il2CppObjectBase)station).TryCast<ChemistryStation>();
			if ((Object)(object)val6 != (Object)null)
			{
				val2 = val6.OutputSlot;
				ChemistryStationConfiguration val7 = ((val6.Configuration == null) ? null : ((Il2CppObjectBase)val6.Configuration).TryCast<ChemistryStationConfiguration>());
				if (val7 != null)
				{
					val3 = DestinationOf(val7.Destination);
					route = val7.DestinationRoute;
				}
			}
			else
			{
				Cauldron val8 = ((Il2CppObjectBase)station).TryCast<Cauldron>();
				if ((Object)(object)val8 != (Object)null)
				{
					val2 = val8.OutputSlot;
					CauldronConfiguration val9 = ((val8.Configuration == null) ? null : ((Il2CppObjectBase)val8.Configuration).TryCast<CauldronConfiguration>());
					if (val9 != null)
					{
						val3 = DestinationOf(val9.Destination);
						route = val9.DestinationRoute;
					}
				}
				else
				{
					LabOven val10 = ((Il2CppObjectBase)station).TryCast<LabOven>();
					if ((Object)(object)val10 != (Object)null)
					{
						val2 = val10.OutputSlot;
						LabOvenConfiguration val11 = ((val10.Configuration == null) ? null : ((Il2CppObjectBase)val10.Configuration).TryCast<LabOvenConfiguration>());
						if (val11 != null)
						{
							val3 = DestinationOf(val11.Destination);
							route = val11.DestinationRoute;
						}
					}
				}
			}
		}
		if (val2 == null || val3 == null)
		{
			return false;
		}
		if (((Il2CppObjectBase)val3).Pointer == ((Il2CppObjectBase)val).Pointer)
		{
			return false;
		}
		info = new SourceInfo
		{
			Station = station,
			Output = val2,
			Entity = val,
			Destination = val3,
			Route = route
		};
		return true;
	}

	private static ITransitEntity DestinationOf(ObjectField field)
	{
		if (field == null || (Object)(object)field.SelectedObject == (Object)null)
		{
			return null;
		}
		return ((Il2CppObjectBase)field.SelectedObject).TryCast<ITransitEntity>();
	}

	private static void StoreRoute(IntPtr key, TransitRoute route, bool owned)
	{
		if (Routes.TryGetValue(key, out var value) && value != null && value.Owned && value.Route != null && value.Route != route)
		{
			SafeDestroy(value.Route);
		}
		if (owned)
		{
			Routes[key] = new RouteHolder
			{
				Route = route,
				Owned = true
			};
		}
		else
		{
			Routes.Remove(key);
		}
	}

	private static void Cleanup(float now)
	{
		if (Reserved.Count == 0)
		{
			return;
		}
		List<IntPtr> list = new List<IntPtr>();
		foreach (KeyValuePair<IntPtr, float> item in Reserved)
		{
			if (now >= item.Value)
			{
				list.Add(item.Key);
			}
		}
		for (int i = 0; i < list.Count; i++)
		{
			Reserved.Remove(list[i]);
		}
	}

	private static bool SameEndpoints(TransitRoute route, ITransitEntity source, ITransitEntity destination)
	{
		try
		{
			return route != null && route.Source != null && route.Destination != null && ((Il2CppObjectBase)route.Source).Pointer == ((Il2CppObjectBase)source).Pointer && ((Il2CppObjectBase)route.Destination).Pointer == ((Il2CppObjectBase)destination).Pointer;
		}
		catch
		{
			return false;
		}
	}

	private static void SafeDestroy(TransitRoute route)
	{
		if (route == null)
		{
			return;
		}
		try
		{
			route.Destroy();
		}
		catch
		{
		}
	}

	private static string SafeName(BuildableItem station)
	{
		try
		{
			return ((Object)(object)station == (Object)null) ? "?" : (((Object)station).name ?? "station");
		}
		catch
		{
			return "station";
		}
	}

	private static string SafeName(ITransitEntity entity)
	{
		try
		{
			return (entity == null) ? "?" : (entity.Name ?? "destination");
		}
		catch
		{
			return "destination";
		}
	}
}
internal static class ChemistFetch
{
	private static readonly Dictionary<IntPtr, float> NextCheck = new Dictionary<IntPtr, float>();

	internal static void Reset()
	{
		NextCheck.Clear();
	}

	internal static void UpdateBehaviourPostfix(Chemist __instance)
	{
		if ((Object)(object)__instance == (Object)null || Config.Enabled == null || !Config.Enabled.Value || Config.EnableChemist == null || !Config.EnableChemist.Value || !Net.IsServer)
		{
			return;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (NextCheck.TryGetValue(((Il2CppObjectBase)__instance).Pointer, out var value) && realtimeSinceStartup < value)
		{
			return;
		}
		float num = ((Config.SearchIntervalSeconds == null) ? 0.75f : Config.SearchIntervalSeconds.Value);
		if (num < 0.1f)
		{
			num = 0.1f;
		}
		NextCheck[((Il2CppObjectBase)__instance).Pointer] = realtimeSinceStartup + num;
		try
		{
			if (((Employee)__instance).Fired || !((Employee)__instance).CanWork())
			{
				return;
			}
			MoveItemBehaviour moveItemBehaviour = ((Employee)__instance).MoveItemBehaviour;
			if ((Object)(object)moveItemBehaviour == (Object)null || ((Behaviour)moveItemBehaviour).Active || ((Employee)__instance).IsAnyWorkInProgress())
			{
				return;
			}
			ChemistConfiguration configuration = __instance.configuration;
			if (configuration == null)
			{
				return;
			}
			Property assignedProperty = ((Employee)__instance).AssignedProperty;
			if (ChainMover.TryMove(__instance, moveItemBehaviour, configuration, assignedProperty))
			{
				return;
			}
			List<TargetRef> list = new List<TargetRef>();
			if (Config.RecipeAwareChemistry == null || Config.RecipeAwareChemistry.Value)
			{
				AddChemistryStations(list, configuration.ChemStations);
			}
			else
			{
				AddStations<ChemistryStation>(list, configuration.ChemStations);
			}
			if (Config.AutoFillLabOvens == null || Config.AutoFillLabOvens.Value)
			{
				AddLabOvens(list, configuration.LabOvens);
			}
			AddStations<Cauldron>(list, configuration.Cauldrons);
			AddMixStations(list, configuration.MixStations, assignedProperty);
			if (list.Count != 0)
			{
				List<FetchCandidate> list2 = StorageIndex.FindCandidates((Employee)(object)__instance, list, requireReachable: true);
				for (int i = 0; i < list2.Count && !FetchService.TryAssign((Employee)(object)__instance, moveItemBehaviour, list2[i]); i++)
				{
				}
			}
		}
		catch (Exception ex)
		{
			Log.Debug("chemist fetch failed: " + ex.Message);
		}
	}

	private static void AddStations<T>(List<TargetRef> targets, List<T> stations) where T : BuildableItem
	{
		if (stations == null)
		{
			return;
		}
		for (int i = 0; i < stations.Count; i++)
		{
			T val = stations[i];
			if (!((Object)(object)val == (Object)null))
			{
				AddTargets(targets, (BuildableItem)(object)val, ((Il2CppObjectBase)(object)val).TryCast<ITransitEntity>());
			}
		}
	}

	private static void AddLabOvens(List<TargetRef> targets, List<LabOven> ovens)
	{
		if (ovens == null)
		{
			return;
		}
		bool flag = Config.LabOvenCookableOnly == null || Config.LabOvenCookableOnly.Value;
		for (int i = 0; i < ovens.Count; i++)
		{
			LabOven val = ovens[i];
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			ITransitEntity val2 = ((Il2CppObjectBase)val).TryCast<ITransitEntity>();
			ItemSlot ingredientSlot = val.IngredientSlot;
			if (val2 == null || ingredientSlot == null)
			{
				continue;
			}
			try
			{
				if (val.CurrentOperation != null || ingredientSlot.IsLocked || ingredientSlot.IsAddLocked)
				{
					continue;
				}
				goto IL_006b;
			}
			catch
			{
			}
			continue;
			IL_006b:
			TargetRef targetRef = new TargetRef
			{
				Entity = val2,
				Slot = ingredientSlot
			};
			if (flag)
			{
				targetRef.ItemFilter = CookableItems.IsCookable;
			}
			targets.Add(targetRef);
		}
	}

	private static void AddChemistryStations(List<TargetRef> targets, List<ChemistryStation> stations)
	{
		if (stations == null)
		{
			return;
		}
		bool flag = Config.ChemistryRequirePositiveFilter == null || Config.ChemistryRequirePositiveFilter.Value;
		for (int i = 0; i < stations.Count; i++)
		{
			ChemistryStation val = stations[i];
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			ITransitEntity val2 = ((Il2CppObjectBase)val).TryCast<ITransitEntity>();
			if (val2 == null)
			{
				continue;
			}
			ChemistryFilters.ApplyIfNeeded(val);
			StationRecipe recipe = ChemistryFilters.GetRecipe(val);
			if ((Object)(object)recipe == (Object)null)
			{
				Log.Debug("chemistry station without recipe skipped: " + SafeStationName(val));
				continue;
			}
			Il2CppReferenceArray<ItemSlot> ingredientSlots = val.IngredientSlots;
			List<IngredientQuantity> ingredients = recipe.Ingredients;
			if (ingredientSlots == null || ingredients == null)
			{
				continue;
			}
			int num = Math.Min(Math.Min(((Il2CppArrayBase<ItemSlot>)(object)ingredientSlots).Length, ingredients.Count), 3);
			for (int j = 0; j < num; j++)
			{
				ItemSlot val3 = ((Il2CppArrayBase<ItemSlot>)(object)ingredientSlots)[j];
				if (val3 == null)
				{
					continue;
				}
				if (flag && !SlotFilters.HasPositiveWhitelist(val3))
				{
					Log.Debug("chemistry ingredient slot " + j + " skipped: no positive filter.");
					continue;
				}
				HashSet<string> hashSet = ChemistryFilters.GroupIds(ingredients[j]);
				if (hashSet.Count == 0)
				{
					continue;
				}
				HashSet<string> hashSet2 = SlotFilters.WhitelistIds(val3);
				HashSet<string> hashSet3 = ((hashSet2.Count != 0) ? Intersect(hashSet, hashSet2) : (flag ? new HashSet<string>(StringComparer.OrdinalIgnoreCase) : hashSet));
				if (hashSet3.Count == 0)
				{
					Log.Debug("chemistry ingredient slot " + j + " skipped: filter does not match the recipe.");
					continue;
				}
				ItemInstance itemInstance = val3.ItemInstance;
				if (itemInstance != null)
				{
					string text;
					try
					{
						text = (((Object)(object)itemInstance.Definition == (Object)null) ? null : ((BaseItemDefinition)itemInstance.Definition).ID);
					}
					catch
					{
						text = null;
					}
					if (string.IsNullOrWhiteSpace(text) || !hashSet3.Contains(text))
					{
						Log.Debug("chemistry ingredient slot " + j + " contains an item outside the recipe; skipped.");
						continue;
					}
				}
				targets.Add(new TargetRef
				{
					Entity = val2,
					Slot = val3,
					AllowedItemIds = hashSet3
				});
			}
		}
	}

	private static HashSet<string> Intersect(HashSet<string> a, HashSet<string> b)
	{
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		foreach (string item in a)
		{
			if (b.Contains(item))
			{
				hashSet.Add(item);
			}
		}
		return hashSet;
	}

	private static string SafeStationName(ChemistryStation station)
	{
		try
		{
			return ((Object)(object)station == (Object)null) ? "?" : (station.Name ?? "chemistry station");
		}
		catch
		{
			return "chemistry station";
		}
	}

	private static void AddMixStations(List<TargetRef> targets, List<MixingStation> stations, Property property)
	{
		if (stations == null)
		{
			return;
		}
		bool flag = Config.ChainAwareMixers == null || Config.ChainAwareMixers.Value;
		for (int i = 0; i < stations.Count; i++)
		{
			MixingStation val = stations[i];
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			ITransitEntity val2 = ((Il2CppObjectBase)val).TryCast<ITransitEntity>();
			if (val2 == null)
			{
				continue;
			}
			if (!flag)
			{
				List<ItemSlot> inputSlots = val.InputSlots;
				if (inputSlots != null && inputSlots.Count > 0)
				{
					AddTargets(targets, (BuildableItem)(object)val, val2);
					continue;
				}
				AddSlot(targets, val2, val.MixerSlot);
				AddSlot(targets, val2, val.ProductSlot);
			}
			else
			{
				AddSlot(targets, val2, val.MixerSlot);
				if ((Object)(object)property == (Object)null || !MixerChain.IsDestination(property, val))
				{
					AddSlot(targets, val2, val.ProductSlot);
				}
			}
		}
	}

	private static void AddTargets(List<TargetRef> targets, BuildableItem station, ITransitEntity transit)
	{
		if (transit == null)
		{
			return;
		}
		List<ItemSlot> inputSlots = transit.InputSlots;
		if (inputSlots != null)
		{
			for (int i = 0; i < inputSlots.Count; i++)
			{
				AddSlot(targets, transit, inputSlots[i]);
			}
		}
	}

	private static void AddSlot(List<TargetRef> targets, ITransitEntity entity, ItemSlot slot)
	{
		if (slot != null)
		{
			targets.Add(new TargetRef
			{
				Entity = entity,
				Slot = slot
			});
		}
	}
}
internal static class ChemistryFilters
{
	private static float _nextScan;

	internal static void Reset()
	{
		_nextScan = 0f;
	}

	internal static void Tick()
	{
		if (!Enabled() || !Net.IsServer)
		{
			return;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (realtimeSinceStartup < _nextScan)
		{
			return;
		}
		float num = ((Config.ChemistryFilterRefreshSeconds == null) ? 1f : Config.ChemistryFilterRefreshSeconds.Value);
		if (num < 0.25f)
		{
			num = 0.25f;
		}
		_nextScan = realtimeSinceStartup + num;
		try
		{
			HashSet<IntPtr> seen = new HashSet<IntPtr>();
			ScanProperties(seen);
			ScanBusinesses(seen);
		}
		catch (Exception ex)
		{
			Log.Debug("chemistry filter scan failed: " + ex.Message);
		}
	}

	internal static void SetRecipePostfix(StationRecipeField __instance, StationRecipe recipe)
	{
		if (!Enabled() || !Net.IsServer)
		{
			return;
		}
		try
		{
			ChemistryStationConfiguration val = ((__instance == null || ((ConfigField)__instance).ParentConfig == null) ? null : ((Il2CppObjectBase)((ConfigField)__instance).ParentConfig).TryCast<ChemistryStationConfiguration>());
			ChemistryStation val2 = ((val == null) ? null : val.Station);
			if ((Object)(object)val2 != (Object)null)
			{
				ApplyIfNeeded(val2, recipe);
			}
		}
		catch (Exception ex)
		{
			Log.Debug("chemistry recipe postfix failed: " + ex.Message);
		}
	}

	internal static void ApplyIfNeeded(ChemistryStation station, StationRecipe recipe = null)
	{
		if ((Object)(object)station == (Object)null || !Enabled() || !Net.IsServer || !StoragePermissions.IsLoaded)
		{
			return;
		}
		string text = StationGuid(station);
		if (string.IsNullOrWhiteSpace(text))
		{
			return;
		}
		recipe = recipe ?? GetRecipe(station);
		string text2 = RecipeId(recipe);
		string appliedChemistryRecipe = StoragePermissions.GetAppliedChemistryRecipe(text);
		if (text2 == null)
		{
			if (appliedChemistryRecipe != null)
			{
				ClearFilters(station);
				StoragePermissions.RemoveAppliedChemistryRecipe(text);
				Log.Debug("chemistry station " + SafeName(station) + ": recipe cleared, filters reset.");
			}
		}
		else if (!(appliedChemistryRecipe == text2) && ApplyFilters(station, recipe))
		{
			StoragePermissions.SetAppliedChemistryRecipe(text, text2);
			Log.Debug("chemistry station " + SafeName(station) + ": recipe " + text2 + " applied to ingredient slots.");
		}
	}

	internal static StationRecipe GetRecipe(ChemistryStation station)
	{
		if ((Object)(object)station == (Object)null)
		{
			return null;
		}
		try
		{
			ChemistryStationConfiguration val = ((station.Configuration == null) ? null : ((Il2CppObjectBase)station.Configuration).TryCast<ChemistryStationConfiguration>());
			return (val == null || val.Recipe == null) ? null : val.Recipe.SelectedRecipe;
		}
		catch
		{
			return null;
		}
	}

	internal static string RecipeId(StationRecipe recipe)
	{
		if ((Object)(object)recipe == (Object)null)
		{
			return null;
		}
		try
		{
			string recipeID = recipe.RecipeID;
			if (!string.IsNullOrWhiteSpace(recipeID))
			{
				return recipeID;
			}
		}
		catch
		{
		}
		try
		{
			string name = ((Object)recipe).name;
			return string.IsNullOrWhiteSpace(name) ? null : name;
		}
		catch
		{
			return null;
		}
	}

	internal static HashSet<string> GroupIds(IngredientQuantity group)
	{
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		if (group == null)
		{
			return hashSet;
		}
		try
		{
			List<ItemDefinition> items = group.Items;
			if (items != null)
			{
				for (int i = 0; i < items.Count; i++)
				{
					AddDefinition(hashSet, items[i]);
				}
			}
		}
		catch
		{
		}
		try
		{
			AddDefinition(hashSet, group.Item);
		}
		catch
		{
		}
		return hashSet;
	}

	private static void ApplyIfNeededFromScan(ChemistryStation station, HashSet<IntPtr> seen)
	{
		if (!((Object)(object)station == (Object)null) && seen.Add(((Il2CppObjectBase)station).Pointer))
		{
			ApplyIfNeeded(station);
		}
	}

	private static void ScanProperties(HashSet<IntPtr> seen)
	{
		List<Property> ownedProperties = Property.OwnedProperties;
		if (ownedProperties == null)
		{
			return;
		}
		for (int i = 0; i < ownedProperties.Count; i++)
		{
			Property val = ownedProperties[i];
			if (!((Object)(object)val == (Object)null))
			{
				ScanBuildables(val.BuildableItems, seen);
			}
		}
	}

	private static void ScanBusinesses(HashSet<IntPtr> seen)
	{
		List<Business> businesses = Business.Businesses;
		if (businesses == null)
		{
			return;
		}
		for (int i = 0; i < businesses.Count; i++)
		{
			Business val = businesses[i];
			if (!((Object)(object)val == (Object)null))
			{
				ScanBuildables(((Property)val).BuildableItems, seen);
			}
		}
	}

	private static void ScanBuildables(List<BuildableItem> items, HashSet<IntPtr> seen)
	{
		if (items == null)
		{
			return;
		}
		for (int i = 0; i < items.Count; i++)
		{
			BuildableItem val = items[i];
			if (!((Object)(object)val == (Object)null))
			{
				ChemistryStation val2 = ((Il2CppObjectBase)val).TryCast<ChemistryStation>();
				if (!((Object)(object)val2 == (Object)null))
				{
					ApplyIfNeededFromScan(val2, seen);
				}
			}
		}
	}

	private static bool ApplyFilters(ChemistryStation station, StationRecipe recipe)
	{
		try
		{
			Il2CppReferenceArray<ItemSlot> ingredientSlots = station.IngredientSlots;
			if (ingredientSlots == null || ((Il2CppArrayBase<ItemSlot>)(object)ingredientSlots).Length == 0)
			{
				return false;
			}
			List<IngredientQuantity> val = (((Object)(object)recipe == (Object)null) ? null : recipe.Ingredients);
			int num = val?.Count ?? 0;
			int num2 = Math.Min(((Il2CppArrayBase<ItemSlot>)(object)ingredientSlots).Length, 3);
			for (int i = 0; i < num2; i++)
			{
				SlotFilter val2 = SlotFilters.BuildWhitelist((i < num) ? GroupIds(val[i]) : new HashSet<string>(StringComparer.OrdinalIgnoreCase));
				((Il2CppArrayBase<ItemSlot>)(object)ingredientSlots)[i].SetPlayerFilter(val2, false);
			}
			if (num > num2)
			{
				Log.Debug("chemistry station " + SafeName(station) + ": recipe has " + num + " ingredient groups, the station only has " + num2 + " slots.");
			}
			return true;
		}
		catch (Exception ex)
		{
			Log.Debug("chemistry filter apply failed: " + ex.Message);
			return false;
		}
	}

	private static void ClearFilters(ChemistryStation station)
	{
		try
		{
			Il2CppReferenceArray<ItemSlot> ingredientSlots = station.IngredientSlots;
			if (ingredientSlots == null)
			{
				return;
			}
			for (int i = 0; i < ((Il2CppArrayBase<ItemSlot>)(object)ingredientSlots).Length; i++)
			{
				if (((Il2CppArrayBase<ItemSlot>)(object)ingredientSlots)[i] != null)
				{
					((Il2CppArrayBase<ItemSlot>)(object)ingredientSlots)[i].SetPlayerFilter(SlotFilters.BuildNone(), false);
				}
			}
		}
		catch (Exception ex)
		{
			Log.Debug("chemistry filter clear failed: " + ex.Message);
		}
	}

	private static void AddDefinition(HashSet<string> ids, ItemDefinition definition)
	{
		if ((Object)(object)definition == (Object)null)
		{
			return;
		}
		try
		{
			string iD = ((BaseItemDefinition)definition).ID;
			if (!string.IsNullOrWhiteSpace(iD))
			{
				ids.Add(iD);
			}
		}
		catch
		{
		}
	}

	private static string StationGuid(ChemistryStation station)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if ((Object)(object)station == (Object)null || ((Il2CppObjectBase)station).Pointer == IntPtr.Zero)
			{
				return null;
			}
			string text = ((object)new ITransitEntity(((Il2CppObjectBase)station).Pointer).GUID/*cast due to .constrained prefix*/).ToString();
			return string.IsNullOrWhiteSpace(text) ? null : text;
		}
		catch
		{
			return null;
		}
	}

	private static string SafeName(ChemistryStation station)
	{
		try
		{
			return ((Object)(object)station == (Object)null) ? "?" : (station.Name ?? "chemistry station");
		}
		catch
		{
			return "chemistry station";
		}
	}

	private static bool Enabled()
	{
		if (Config.Enabled != null && Config.Enabled.Value && Config.AutoFilterChemistrySlots != null)
		{
			return Config.AutoFilterChemistrySlots.Value;
		}
		return false;
	}
}
internal static class Config
{
	internal static MelonPreferences_Category Category;

	internal static MelonPreferences_Entry<bool> Enabled;

	internal static MelonPreferences_Entry<bool> DefaultStorageAccess;

	internal static MelonPreferences_Entry<bool> EnableBotanist;

	internal static MelonPreferences_Entry<bool> EnableChemist;

	internal static MelonPreferences_Entry<bool> RecipeAwareChemistry;

	internal static MelonPreferences_Entry<bool> AutoFilterChemistrySlots;

	internal static MelonPreferences_Entry<bool> ChemistryRequirePositiveFilter;

	internal static MelonPreferences_Entry<float> ChemistryFilterRefreshSeconds;

	internal static MelonPreferences_Entry<bool> EnablePackager;

	internal static MelonPreferences_Entry<string> PackagingProductAutoFillMode;

	internal static MelonPreferences_Entry<string> BrickPressProductAutoFillMode;

	internal static MelonPreferences_Entry<bool> PreferVanillaSupply;

	internal static MelonPreferences_Entry<bool> ChainAwareMixers;

	internal static MelonPreferences_Entry<bool> AutoFillLabOvens;

	internal static MelonPreferences_Entry<bool> LabOvenCookableOnly;

	internal static MelonPreferences_Entry<bool> AllowDeliveryVehicleAccess;

	internal static MelonPreferences_Entry<bool> PreferDeliveryVehicles;

	internal static MelonPreferences_Entry<bool> EnableChainMover;

	internal static MelonPreferences_Entry<float> ChainMoveDelaySeconds;

	internal static MelonPreferences_Entry<float> SearchIntervalSeconds;

	internal static MelonPreferences_Entry<string> Language;

	internal static MelonPreferences_Entry<bool> DebugLogging;

	internal static void Init()
	{
		Category = MelonPreferences.CreateCategory("SmartStorage", "The Employees Know");
		Enabled = Category.CreateEntry<bool>("Enabled", true, "Enabled", "Turn the whole mod on or off. Employee behaviour is unchanged when this is off.", false, false, (ValueValidator)null, (string)null);
		DefaultStorageAccess = Category.CreateEntry<bool>("DefaultStorageAccess", true, "New storages are accessible", "When on, every storage starts available to employees. Turn a storage off with the button in its storage menu.", false, false, (ValueValidator)null, (string)null);
		EnableBotanist = Category.CreateEntry<bool>("EnableBotanist", true, "Botanists use enabled storages", "Botanists may take soil, seeds, additives and spawn from enabled storages on their property.", false, false, (ValueValidator)null, (string)null);
		EnableChemist = Category.CreateEntry<bool>("EnableChemist", true, "Chemists fetch from enabled storages", "Chemists fetch ingredients for their assigned stations from enabled storages on their property.", false, false, (ValueValidator)null, (string)null);
		RecipeAwareChemistry = Category.CreateEntry<bool>("RecipeAwareChemistry", true, "Recipe-aware chemistry stations", "Chemists only fetch the ingredients required by the recipe selected on each chemistry station.", false, false, (ValueValidator)null, (string)null);
		AutoFilterChemistrySlots = Category.CreateEntry<bool>("AutoFilterChemistrySlots", true, "Apply recipe filters to ingredient slots", "When a recipe is selected, The Employees Know sets the three ingredient slot filters from that recipe. Removing a filter by hand leaves it removed until you change the recipe.", false, false, (ValueValidator)null, (string)null);
		ChemistryRequirePositiveFilter = Category.CreateEntry<bool>("ChemistryRequirePositiveFilter", true, "Only fill filtered ingredient slots", "A chemist only auto-fills an ingredient slot that has a positive whitelist. Slots without a filter are left alone.", false, false, (ValueValidator)null, (string)null);
		ChemistryFilterRefreshSeconds = Category.CreateEntry<float>("ChemistryFilterRefreshSeconds", 1f, "Chemistry recipe scan interval (seconds)", "How often The Employees Know looks for chemistry stations whose recipe changed. Applied recipes are remembered per save.", false, false, (ValueValidator)null, (string)null);
		EnablePackager = Category.CreateEntry<bool>("EnablePackager", true, "Handlers fetch from enabled storages", "Packagers fetch inputs for their assigned packaging stations and brick presses from enabled storages.", false, false, (ValueValidator)null, (string)null);
		PackagingProductAutoFillMode = Category.CreateEntry<string>("PackagingProductAutoFillMode", "FilteredOnly", "Packaging product auto-fill", "Off, FilteredOnly or Any. FilteredOnly lets handlers fill a packaging station's product slot only when that slot has a whitelist filter. Without a filter there is no automatic fill.", false, false, (ValueValidator)null, (string)null);
		BrickPressProductAutoFillMode = Category.CreateEntry<string>("BrickPressProductAutoFillMode", "FilteredOnly", "Brick press product auto-fill", "Off, FilteredOnly or Any. FilteredOnly lets handlers fill brick press product slots only when they have a whitelist filter.", false, false, (ValueValidator)null, (string)null);
		PreferVanillaSupply = Category.CreateEntry<bool>("PreferVanillaSupply", true, "Prefer the vanilla supply storage", "If a botanist already has a supply storage selected and it contains the needed item, keep using it before searching other storages.", false, false, (ValueValidator)null, (string)null);
		ChainAwareMixers = Category.CreateEntry<bool>("ChainAwareMixers", true, "Chain-aware mixing stations", "On: only the first mixer of a chain (one that is not another mixer's destination) may receive product from storage. Later mixers wait for the previous mixer's output. Off: old behaviour, every mixer input may be filled.", false, false, (ValueValidator)null, (string)null);
		AutoFillLabOvens = Category.CreateEntry<bool>("AutoFillLabOvens", true, "Chemists fill lab ovens", "Allow chemists to fetch ingredients for assigned lab ovens.", false, false, (ValueValidator)null, (string)null);
		LabOvenCookableOnly = Category.CreateEntry<bool>("LabOvenCookableOnly", true, "Lab ovens only accept cookable items", "The chemist only brings items that have a CookableModule, so it does not waste trips on things the oven cannot process.", false, false, (ValueValidator)null, (string)null);
		AllowDeliveryVehicleAccess = Category.CreateEntry<bool>("AllowDeliveryVehicleAccess", true, "Employees can use delivery vehicles", "While a delivery van is parked at the property, employees may take items from it like a temporary storage.", false, false, (ValueValidator)null, (string)null);
		PreferDeliveryVehicles = Category.CreateEntry<bool>("PreferDeliveryVehicles", true, "Prefer delivery vehicles as a source", "When an item is both in a delivery van and in a normal storage, employees take it from the van first.", false, false, (ValueValidator)null, (string)null);
		EnableChainMover = Category.CreateEntry<bool>("EnableChainMover", true, "Fallback chain output mover", "If the vanilla chemist does not move a mixer's output to the next mixer, The Employees Know moves it after the delay below. Vanilla movement always wins.", false, false, (ValueValidator)null, (string)null);
		ChainMoveDelaySeconds = Category.CreateEntry<float>("ChainMoveDelaySeconds", 3f, "Chain mover fallback delay (seconds)", "How long an output must sit in a mixer before The Employees Know moves it to the next mixer. Increase this if vanilla already moves the output.", false, false, (ValueValidator)null, (string)null);
		SearchIntervalSeconds = Category.CreateEntry<float>("SearchIntervalSeconds", 0.75f, "Search interval (seconds)", "How often an idle employee looks for a new source. Lower values react faster but scan more often.", false, false, (ValueValidator)null, (string)null);
		Language = Category.CreateEntry<string>("Language", "auto", "Language", "auto, en, es, fr, de, pt, ru, zh or ja. Auto follows the operating system language.", false, false, (ValueValidator)null, (string)null);
		DebugLogging = Category.CreateEntry<bool>("DebugLogging", false, "Debug logging", "Write extra lines about storage scans, chosen sources and skipped items.", false, false, (ValueValidator)null, (string)null);
	}
}
internal static class CookableItems
{
	private static readonly Dictionary<IntPtr, bool> Cache = new Dictionary<IntPtr, bool>();

	internal static void Reset()
	{
		Cache.Clear();
	}

	internal static bool IsCookable(ItemInstance item)
	{
		if (item == null || (Object)(object)item.Definition == (Object)null)
		{
			return false;
		}
		IntPtr pointer = ((Il2CppObjectBase)item.Definition).Pointer;
		if (pointer != IntPtr.Zero && Cache.TryGetValue(pointer, out var value))
		{
			return value;
		}
		bool flag = IsCookableUncached(item);
		if (pointer != IntPtr.Zero)
		{
			Cache[pointer] = flag;
		}
		try
		{
			Log.Debug("cookable " + ((BaseItemDefinition)item.Definition).ID + " = " + flag);
		}
		catch
		{
		}
		return flag;
	}

	private static bool IsCookableUncached(ItemInstance item)
	{
		try
		{
			StorableItemDefinition val = ((Il2CppObjectBase)item.Definition).TryCast<StorableItemDefinition>();
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			StationItem stationItem = val.StationItem;
			if ((Object)(object)stationItem == (Object)null)
			{
				return false;
			}
			try
			{
				List<ItemModule> modules = stationItem.Modules;
				if (modules != null)
				{
					for (int i = 0; i < modules.Count; i++)
					{
						ItemModule val2 = modules[i];
						if ((Object)(object)val2 != (Object)null && (Object)(object)((Il2CppObjectBase)val2).TryCast<CookableModule>() != (Object)null)
						{
							return true;
						}
					}
				}
			}
			catch
			{
			}
			try
			{
				if ((Object)(object)((Component)stationItem).GetComponentInChildren<CookableModule>(true) != (Object)null)
				{
					return true;
				}
				if ((Object)(object)((Component)stationItem).GetComponent<CookableModule>() != (Object)null)
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}
		catch (Exception ex)
		{
			Log.Debug("cookable check failed for " + (((Object)(object)item.Definition != (Object)null) ? ((BaseItemDefinition)item.Definition).ID : "?") + ": " + ex.Message);
			return false;
		}
	}
}
internal static class FetchService
{
	private static readonly Dictionary<IntPtr, TransitRoute> Routes = new Dictionary<IntPtr, TransitRoute>();

	internal static bool TryAssign(Employee employee, MoveItemBehaviour move, FetchCandidate candidate)
	{
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Expected O, but got Unknown
		if ((Object)(object)employee == (Object)null || (Object)(object)move == (Object)null || candidate == null)
		{
			return false;
		}
		if (candidate.Source == null || !candidate.Source.IsValid)
		{
			return false;
		}
		if (candidate.Target == null || candidate.Target.Entity == null)
		{
			return false;
		}
		if (candidate.Item == null || (Object)(object)candidate.Item.Definition == (Object)null)
		{
			return false;
		}
		try
		{
			if (((Behaviour)move).Active)
			{
				return false;
			}
		}
		catch
		{
			return false;
		}
		TransitRoute val = null;
		bool flag = false;
		try
		{
			val = new TransitRoute(candidate.Source.Transit, candidate.Target.Entity);
			if (!move.CanGetToSource(val) || !move.CanGetToDestination(val))
			{
				return false;
			}
			string iD = ((BaseItemDefinition)candidate.Item.Definition).ID;
			if (string.IsNullOrWhiteSpace(iD))
			{
				return false;
			}
			if (!move.IsTransitRouteValid(val, iD))
			{
				return false;
			}
			int num = candidate.Capacity;
			if (num < 1)
			{
				num = 1;
			}
			if (Routes.TryGetValue(((Il2CppObjectBase)employee).Pointer, out var value) && value != null)
			{
				try
				{
					value.Destroy();
				}
				catch
				{
				}
				Routes.Remove(((Il2CppObjectBase)employee).Pointer);
			}
			Routes[((Il2CppObjectBase)employee).Pointer] = val;
			flag = true;
			move.Initialize(val, candidate.Item.GetCopy(1), num, false);
			((Behaviour)move).Enable();
			StorageIndex.Reserve(candidate.SourceSlot);
			Log.Debug("assigned " + num + "x " + iD + " from " + candidate.Source.Name + " to " + SafeName((Il2CppObjectBase)(object)candidate.Target.Entity) + " for " + SafeName((Il2CppObjectBase)(object)employee));
			return true;
		}
		catch (Exception ex)
		{
			Log.Warning("[The Employees Know] Could not assign a fetch to " + SafeName((Il2CppObjectBase)(object)employee) + ": " + ex.Message);
			return false;
		}
		finally
		{
			if (!flag && val != null)
			{
				try
				{
					val.Destroy();
				}
				catch
				{
				}
			}
		}
	}

	internal static void Reset()
	{
		foreach (TransitRoute value in Routes.Values)
		{
			if (value != null)
			{
				try
				{
					value.Destroy();
				}
				catch
				{
				}
			}
		}
		Routes.Clear();
	}

	private static string SafeName(Il2CppObjectBase obj)
	{
		try
		{
			if (obj == null)
			{
				return "?";
			}
			Employee val = obj.TryCast<Employee>();
			if ((Object)(object)val != (Object)null)
			{
				return ((NPC)val).FullName ?? "?";
			}
			ITransitEntity val2 = obj.TryCast<ITransitEntity>();
			if (val2 != null)
			{
				return val2.Name ?? "?";
			}
			return "?";
		}
		catch
		{
			return "?";
		}
	}
}
internal static class L10n
{
	private static readonly Dictionary<string, Dictionary<string, string>> Strings = new Dictionary<string, Dictionary<string, string>>
	{
		["en"] = new Dictionary<string, string>
		{
			["StorageAllowed"] = "Employees: Allowed",
			["StorageBlocked"] = "Employees: Blocked",
			["StorageHint"] = "Click to let employees take items from this storage.",
			["StorageAccessLabel"] = "Employees can take from this storage"
		},
		["es"] = new Dictionary<string, string>
		{
			["StorageAllowed"] = "Empleados: Permitido",
			["StorageBlocked"] = "Empleados: Bloqueado",
			["StorageHint"] = "Haz clic para permitir que los empleados tomen objetos de este almacén.",
			["StorageAccessLabel"] = "Los empleados pueden tomar de este almacén"
		},
		["fr"] = new Dictionary<string, string>
		{
			["StorageAllowed"] = "Employés : autorisés",
			["StorageBlocked"] = "Employés : bloqués",
			["StorageHint"] = "Cliquez pour autoriser les employés à prendre des objets dans ce stockage.",
			["StorageAccessLabel"] = "Les employés peuvent prendre dans ce stockage"
		},
		["de"] = new Dictionary<string, string>
		{
			["StorageAllowed"] = "Mitarbeiter: erlaubt",
			["StorageBlocked"] = "Mitarbeiter: blockiert",
			["StorageHint"] = "Klicken, damit Mitarbeiter Gegenstände aus diesem Lager entnehmen dürfen.",
			["StorageAccessLabel"] = "Mitarbeiter dürfen aus diesem Lager entnehmen"
		},
		["pt"] = new Dictionary<string, string>
		{
			["StorageAllowed"] = "Funcionários: permitido",
			["StorageBlocked"] = "Funcionários: bloqueado",
			["StorageHint"] = "Clique para permitir que os funcionários retirem itens deste armazenamento.",
			["StorageAccessLabel"] = "Os funcionários podem retirar deste armazenamento"
		},
		["ru"] = new Dictionary<string, string>
		{
			["StorageAllowed"] = "Сотрудники: разрешено",
			["StorageBlocked"] = "Сотрудники: запрещено",
			["StorageHint"] = "Нажмите, чтобы разрешить сотрудникам брать предметы из этого хранилища.",
			["StorageAccessLabel"] = "Сотрудники могут брать предметы из этого хранилища"
		},
		["zh"] = new Dictionary<string, string>
		{
			["StorageAllowed"] = "员工:允许",
			["StorageBlocked"] = "员工:禁止",
			["StorageHint"] = "点击允许员工从此存储中取用物品。",
			["StorageAccessLabel"] = "员工可以从此存储中取用物品"
		},
		["ja"] = new Dictionary<string, string>
		{
			["StorageAllowed"] = "従業員:許可",
			["StorageBlocked"] = "従業員:禁止",
			["StorageHint"] = "クリックして、従業員がこの収納からアイテムを取ることを許可します。",
			["StorageAccessLabel"] = "従業員はこの収納からアイテムを取ることができます"
		}
	};

	private static string _language = "en";

	internal static void Init()
	{
		string text = ((Config.Language == null) ? "auto" : (Config.Language.Value ?? "auto").Trim().ToLowerInvariant());
		if (text == "auto" || text.Length == 0)
		{
			text = ((CultureInfo.CurrentUICulture == null) ? "en" : (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName ?? "en").ToLowerInvariant());
		}
		if (text.Length > 2)
		{
			text = text.Substring(0, 2);
		}
		_language = (Strings.ContainsKey(text) ? text : "en");
		Log.Msg("[The Employees Know] UI language: " + _language);
	}

	internal static string Get(string key)
	{
		if (Strings.TryGetValue(_language, out var value) && value.TryGetValue(key, out var value2))
		{
			return value2;
		}
		if (!Strings["en"].TryGetValue(key, out var value3))
		{
			return key;
		}
		return value3;
	}
}
internal static class Log
{
	internal static Instance Logger;

	internal static bool DebugEnabled
	{
		get
		{
			if (Config.DebugLogging != null)
			{
				return Config.DebugLogging.Value;
			}
			return false;
		}
	}

	internal static void Msg(string message)
	{
		try
		{
			Instance logger = Logger;
			if (logger != null)
			{
				logger.Msg(message);
			}
		}
		catch
		{
		}
	}

	internal static void Warning(string message)
	{
		try
		{
			Instance logger = Logger;
			if (logger != null)
			{
				logger.Warning(message);
			}
		}
		catch
		{
		}
	}

	internal static void Error(string message)
	{
		try
		{
			Instance logger = Logger;
			if (logger != null)
			{
				logger.Error(message);
			}
		}
		catch
		{
		}
	}

	internal static void Debug(string message)
	{
		if (DebugEnabled)
		{
			Msg("[debug] " + message);
		}
	}
}
public class Main : MelonMod
{
	public override void OnInitializeMelon()
	{
		Log.Logger = ((MelonBase)this).LoggerInstance;
		Config.Init();
		L10n.Init();
		StoragePermissions.SaveChanged += ResetCaches;
		Patcher.Apply(((MelonBase)this).HarmonyInstance);
		Log.Msg("[The Employees Know] Ready. Employee storage access is " + ((Config.Enabled != null && Config.Enabled.Value) ? "on" : "off") + ".");
	}

	public override void OnUpdate()
	{
		try
		{
			StoragePermissions.SyncToCurrentSave();
		}
		catch (Exception ex)
		{
			Log.Debug("save sync failed: " + ex.Message);
		}
		try
		{
			ChemistryFilters.Tick();
		}
		catch (Exception ex2)
		{
			Log.Debug("chemistry filter tick failed: " + ex2.Message);
		}
		try
		{
			BotanistScheduler.Tick();
		}
		catch (Exception ex3)
		{
			Log.Debug("botanist scheduler tick failed: " + ex3.Message);
		}
		try
		{
			if (Input.GetKeyDown((KeyCode)289))
			{
				DumpDiagnostics();
			}
		}
		catch
		{
		}
	}

	public override void OnGUI()
	{
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		if (StorageToggleUI.NativeOk || Config.Enabled == null || !Config.Enabled.Value)
		{
			return;
		}
		try
		{
			StorageMenu instance = Singleton<StorageMenu>.Instance;
			if ((Object)(object)instance == (Object)null || !instance.IsOpen)
			{
				return;
			}
			StorageRef current = StorageToggleUI.Current;
			if (current != null)
			{
				bool flag = StoragePermissions.IsAllowed(current.Guid);
				GUI.Label(new Rect(30f, 120f, 380f, 42f), L10n.Get("StorageHint"));
				bool flag2 = GUI.Toggle(new Rect(30f, 162f, 380f, 28f), flag, L10n.Get("StorageAccessLabel"));
				if (flag2 != flag)
				{
					StoragePermissions.SetAllowed(current.Guid, flag2);
					Log.Msg("[The Employees Know] " + (current.Name ?? "storage") + ": " + (flag2 ? "employees allowed" : "employees blocked") + ".");
				}
			}
		}
		catch
		{
		}
	}

	public override void OnApplicationQuit()
	{
		try
		{
			StoragePermissions.Flush();
		}
		catch
		{
		}
		ResetCaches();
	}

	private static void ResetCaches()
	{
		FetchService.Reset();
		BotanistSupply.Reset();
		ChemistFetch.Reset();
		PackagerFetch.Reset();
		ChainMover.Reset();
		MixerChain.Reset();
		ChemistryFilters.Reset();
		CookableItems.Reset();
		BotanistScheduler.Reset();
		BotanistDryableMover.Reset();
		StorageIndex.ClearReservations();
	}

	private static void DumpDiagnostics()
	{
		Log.Msg("[The Employees Know] debug: permissions=" + StoragePermissions.Count + " host=" + Net.IsServer);
		try
		{
			List<Property> properties = Property.Properties;
			if (properties == null)
			{
				return;
			}
			for (int i = 0; i < properties.Count; i++)
			{
				Property val = properties[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				List<StorageRef> list = new List<StorageRef>();
				StorageIndex.CollectForProperty(val, allowedOnly: false, list);
				int num = 0;
				for (int j = 0; j < list.Count; j++)
				{
					if (StoragePermissions.IsAllowed(list[j].Guid))
					{
						num++;
					}
				}
				Log.Msg("[The Employees Know] " + val.PropertyName + ": " + num + "/" + list.Count + " storage(s) allowed.");
			}
		}
		catch (Exception ex)
		{
			Log.Debug("diagnostics failed: " + ex.Message);
		}
	}
}
internal static class MixerChain
{
	private sealed class Entry
	{
		internal readonly HashSet<IntPtr> Destinations = new HashSet<IntPtr>();

		internal readonly Dictionary<IntPtr, MixingStation> BySource = new Dictionary<IntPtr, MixingStation>();

		internal float Until;
	}

	private const float CacheSeconds = 1f;

	private static readonly Dictionary<IntPtr, Entry> Cache = new Dictionary<IntPtr, Entry>();

	internal static void Reset()
	{
		Cache.Clear();
	}

	internal static bool IsDestination(Property property, MixingStation station)
	{
		if ((Object)(object)property == (Object)null || (Object)(object)station == (Object)null)
		{
			return false;
		}
		try
		{
			return Get(property).Destinations.Contains(((Il2CppObjectBase)station).Pointer);
		}
		catch (Exception ex)
		{
			Log.Debug("MixerChain.IsDestination failed: " + ex.Message);
			return false;
		}
	}

	internal static MixingStation DestinationOf(MixingStation station)
	{
		if ((Object)(object)station == (Object)null)
		{
			return null;
		}
		try
		{
			MixingStationConfiguration val = ConfigOf(station);
			BuildableItem val2 = ((val == null || val.Destination == null) ? null : val.Destination.SelectedObject);
			return ((Object)(object)val2 == (Object)null) ? null : ((Il2CppObjectBase)val2).TryCast<MixingStation>();
		}
		catch
		{
			return null;
		}
	}

	internal static MixingStationConfiguration ConfigOf(MixingStation station)
	{
		if ((Object)(object)station == (Object)null)
		{
			return null;
		}
		try
		{
			MixingStationConfiguration val = ((station.Configuration == null) ? null : ((Il2CppObjectBase)station.Configuration).TryCast<MixingStationConfiguration>());
			if (val != null)
			{
				return val;
			}
		}
		catch
		{
		}
		try
		{
			PropertyInfo propertyInfo = AccessTools.Property(typeof(MixingStation), "stationConfiguration");
			return (MixingStationConfiguration)((propertyInfo == null) ? null : /*isinst with value type is only supported in some contexts*/);
		}
		catch
		{
			return null;
		}
	}

	private static Entry Get(Property property)
	{
		IntPtr pointer = ((Il2CppObjectBase)property).Pointer;
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (Cache.TryGetValue(pointer, out var value) && realtimeSinceStartup < value.Until)
		{
			return value;
		}
		Entry entry = new Entry
		{
			Until = realtimeSinceStartup + 1f
		};
		Cache[pointer] = entry;
		List<BuildableItem> buildableItems = property.BuildableItems;
		if (buildableItems == null)
		{
			return entry;
		}
		for (int i = 0; i < buildableItems.Count; i++)
		{
			BuildableItem val = buildableItems[i];
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			MixingStation val2 = ((Il2CppObjectBase)val).TryCast<MixingStation>();
			if (!((Object)(object)val2 == (Object)null))
			{
				MixingStation val3 = DestinationOf(val2);
				if (!((Object)(object)val3 == (Object)null))
				{
					entry.Destinations.Add(((Il2CppObjectBase)val3).Pointer);
					entry.BySource[((Il2CppObjectBase)val2).Pointer] = val3;
				}
			}
		}
		return entry;
	}
}
internal static class Net
{
	internal static bool IsServer
	{
		get
		{
			try
			{
				NetworkManager networkManager = InstanceFinder.NetworkManager;
				return (Object)(object)networkManager != (Object)null && networkManager.IsServer;
			}
			catch
			{
				return false;
			}
		}
	}
}
internal static class PackagerFetch
{
	private static readonly Dictionary<IntPtr, float> NextCheck = new Dictionary<IntPtr, float>();

	private static readonly Dictionary<IntPtr, int> RouteFrame = new Dictionary<IntPtr, int>();

	internal static void Reset()
	{
		NextCheck.Clear();
		RouteFrame.Clear();
	}

	internal static void UpdateBehaviourPostfix(Packager __instance)
	{
		if ((Object)(object)__instance == (Object)null || Config.Enabled == null || !Config.Enabled.Value || Config.EnablePackager == null || !Config.EnablePackager.Value || !Net.IsServer)
		{
			return;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (NextCheck.TryGetValue(((Il2CppObjectBase)__instance).Pointer, out var value) && realtimeSinceStartup < value)
		{
			return;
		}
		float num = ((Config.SearchIntervalSeconds == null) ? 0.75f : Config.SearchIntervalSeconds.Value);
		if (num < 0.1f)
		{
			num = 0.1f;
		}
		NextCheck[((Il2CppObjectBase)__instance).Pointer] = realtimeSinceStartup + num;
		try
		{
			if (((Employee)__instance).Fired || !((Employee)__instance).CanWork())
			{
				return;
			}
			MoveItemBehaviour moveItemBehaviour = ((Employee)__instance).MoveItemBehaviour;
			if ((Object)(object)moveItemBehaviour == (Object)null || ((Behaviour)moveItemBehaviour).Active || (RouteFrame.TryGetValue(((Il2CppObjectBase)__instance).Pointer, out var value2) && value2 == Time.frameCount) || ((Employee)__instance).IsAnyWorkInProgress())
			{
				return;
			}
			PackagerConfiguration configuration = __instance.configuration;
			if (configuration == null)
			{
				return;
			}
			List<TargetRef> list = new List<TargetRef>();
			AddPackagingStations(list, configuration.AssignedStations);
			AddBrickPresses(list, configuration.AssignedBrickPresses);
			if (list.Count != 0)
			{
				List<FetchCandidate> list2 = StorageIndex.FindCandidates((Employee)(object)__instance, list, requireReachable: true);
				for (int i = 0; i < list2.Count && !FetchService.TryAssign((Employee)(object)__instance, moveItemBehaviour, list2[i]); i++)
				{
				}
			}
		}
		catch (Exception ex)
		{
			Log.Debug("packager fetch failed: " + ex.Message);
		}
	}

	internal static void GetTransitRouteReadyPostfix(Packager __instance, AdvancedTransitRoute __result)
	{
		if ((Object)(object)__instance != (Object)null && __result != null)
		{
			RouteFrame[((Il2CppObjectBase)__instance).Pointer] = Time.frameCount;
		}
	}

	private static void AddPackagingStations(List<TargetRef> targets, List<PackagingStation> stations)
	{
		if (stations == null)
		{
			return;
		}
		string text = ((Config.PackagingProductAutoFillMode == null) ? "FilteredOnly" : (Config.PackagingProductAutoFillMode.Value ?? "FilteredOnly"));
		for (int i = 0; i < stations.Count; i++)
		{
			PackagingStation val = stations[i];
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			ITransitEntity val2 = ((Il2CppObjectBase)val).TryCast<ITransitEntity>();
			if (val2 != null)
			{
				AddSlot(targets, val2, val.PackagingSlot);
				ItemSlot productSlot = val.ProductSlot;
				if (AllowProductSlot(text, productSlot))
				{
					AddSlot(targets, val2, productSlot);
				}
				else if (productSlot != null)
				{
					Log.Debug("packaging product slot skipped (mode=" + text + ", whitelist=" + HasPositiveWhitelist(productSlot) + ")");
				}
			}
		}
	}

	private static void AddBrickPresses(List<TargetRef> targets, List<BrickPress> presses)
	{
		if (presses == null)
		{
			return;
		}
		string text = ((Config.BrickPressProductAutoFillMode == null) ? "FilteredOnly" : (Config.BrickPressProductAutoFillMode.Value ?? "FilteredOnly"));
		for (int i = 0; i < presses.Count; i++)
		{
			BrickPress val = presses[i];
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			ITransitEntity val2 = ((Il2CppObjectBase)val).TryCast<ITransitEntity>();
			if (val2 == null)
			{
				continue;
			}
			Il2CppReferenceArray<ItemSlot> productSlots = val.ProductSlots;
			if (productSlots == null)
			{
				continue;
			}
			for (int j = 0; j < ((Il2CppArrayBase<ItemSlot>)(object)productSlots).Length; j++)
			{
				ItemSlot val3 = ((Il2CppArrayBase<ItemSlot>)(object)productSlots)[j];
				if (AllowProductSlot(text, val3))
				{
					AddSlot(targets, val2, val3);
				}
				else if (val3 != null)
				{
					Log.Debug("brick press product slot skipped (mode=" + text + ", whitelist=" + HasPositiveWhitelist(val3) + ")");
				}
			}
		}
	}

	private static void AddTargets(List<TargetRef> targets, ITransitEntity transit, List<ItemSlot> slots)
	{
		if (slots != null)
		{
			for (int i = 0; i < slots.Count; i++)
			{
				AddSlot(targets, transit, slots[i]);
			}
		}
	}

	private static bool AllowProductSlot(string mode, ItemSlot slot)
	{
		if (slot == null)
		{
			return false;
		}
		if (string.Equals(mode, "Any", StringComparison.OrdinalIgnoreCase))
		{
			return true;
		}
		if (string.Equals(mode, "Off", StringComparison.OrdinalIgnoreCase))
		{
			return false;
		}
		return HasPositiveWhitelist(slot);
	}

	private static bool HasPositiveWhitelist(ItemSlot slot)
	{
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Invalid comparison between Unknown and I4
		if (slot == null)
		{
			return false;
		}
		try
		{
			SlotFilter playerFilter = slot.PlayerFilter;
			if (playerFilter == null)
			{
				return false;
			}
			if ((int)playerFilter.Type != 1)
			{
				return false;
			}
			List<string> itemIDs = playerFilter.ItemIDs;
			return itemIDs != null && itemIDs.Count > 0;
		}
		catch
		{
			return false;
		}
	}

	private static void AddSlot(List<TargetRef> targets, ITransitEntity entity, ItemSlot slot)
	{
		if (slot != null)
		{
			targets.Add(new TargetRef
			{
				Entity = entity,
				Slot = slot
			});
		}
	}
}
internal static class Patcher
{
	internal static void Apply(Harmony harmony)
	{
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Expected O, but got Unknown
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: Expected O, but got Unknown
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dc: Expected O,