Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of The Employees Know v0.2.1
SmartStorage.dll
Decompiled 12 hours ago
The result has been truncated due to the large size, download it to view full contents!
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,