Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CraftFromDrawersAndChests v1.0.4
plugins\CraftFromDrawersAndChests.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CraftFromDrawersAndChests.Logic; using CraftFromDrawersAndChests.Pull; using CraftFromDrawersAndChests.Storage; using HarmonyLib; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using MultiUserChest; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } } namespace CraftFromDrawersAndChests { public static class ModInfo { public const string ModName = "CraftFromDrawersAndChests"; public const string ModGuid = "NorCal_Nerds.CraftFromDrawersAndChests"; public const string Version = "1.0.4"; public const string ThunderstoreTeam = "NorCal_Nerds"; public const string ThunderstorePackage = "CraftFromDrawersAndChests"; public const string GitHubRepo = "ghstwhl/CraftFromDrawersAndChests"; public const string ConsoleCommand = "craftfromdrawersandchests"; public const string ConsoleCommandAlias = "cfdac"; } [BepInPlugin("NorCal_Nerds.CraftFromDrawersAndChests", "CraftFromDrawersAndChests", "1.0.4")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInIncompatibility("aedenthorn.CraftFromContainers")] [BepInIncompatibility("Azumatt.AzuCraftyBoxes")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [SynchronizationMode(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public static Plugin Instance; public static Harmony Harmony; internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger; private void Awake() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown Instance = this; Harmony = new Harmony("NorCal_Nerds.CraftFromDrawersAndChests"); try { PluginConfig.Init(((BaseUnityPlugin)this).Config); PluginConfig.ReadInto(PluginConfig.Flags); } catch (Exception ex) { if (PluginConfig.Flags != null) { PluginConfig.Flags.ModEnabled = false; } Log.LogError((object)("CraftFromDrawersAndChests: configuration initialisation failed; the mod is being left fully disabled until this is fixed (Harmony patches are still applied, but every config-gated feature is inert). Cause: " + ex)); } Harmony.PatchAll(typeof(Plugin).Assembly); LogPatchReport(); } private static void LogPatchReport() { List<string> list = new List<string>(); foreach (MethodBase patchedMethod in Harmony.GetPatchedMethods()) { list.Add(patchedMethod.DeclaringType.Name + "." + patchedMethod.Name); } list.Sort(); int count = list.Count; Log.LogInfo((object)("CraftFromDrawersAndChests 1.0.4: Harmony patched " + count + " game method(s): " + string.Join(", ", list.ToArray()) + " (Harmony's no-argument PatchAll() would have inferred '" + InferPatchAllAssembly() + "'.)")); if (count == 0) { Log.LogError((object)"CraftFromDrawersAndChests: Harmony applied ZERO patches, so every feature is inert (no chest pulling, no drawer pulling, no per-storage switch, no console command). Nothing else in this log marks that state."); } } [MethodImpl(MethodImplOptions.NoInlining)] private static string InferPatchAllAssembly() { try { StackFrame frame = new StackTrace().GetFrame(1); if (frame == null) { return "<null frame>"; } MethodBase method = frame.GetMethod(); if (method == null) { return "<null method>"; } Type reflectedType = method.ReflectedType; if (reflectedType == null) { return "<null reflected type>"; } return reflectedType.Assembly.GetName().Name; } catch (Exception ex) { return "<threw " + ex.GetType().Name + ">"; } } } public static class PluginConfig { public static ConfigFile File; public static FeatureFlags Flags; private static ConfigEntry<bool> modEnabled; private static ConfigEntry<bool> isDebug; private static ConfigEntry<float> maxRange; private static ConfigEntry<string> pullItemsKey; private static ConfigEntry<string> toggleStorageKey; private static ConfigEntry<string> preventModKey; private static ConfigEntry<string> fillAllModKey; private static ConfigEntry<bool> switchPrevent; private static ConfigEntry<bool> enableChests; private static ConfigEntry<bool> enableDrawers; private static ConfigEntry<bool> leaveOne; private static ConfigEntry<bool> pullByDistance; private static ConfigEntry<bool> pullFuel; private static ConfigEntry<bool> pullOre; private static ConfigEntry<bool> pullCooking; private static ConfigEntry<string> fuelDisallowTypes; private static ConfigEntry<string> oreDisallowTypes; private static ConfigEntry<string> resourceString; private static ConfigEntry<string> pulledMessage; private static ConfigEntry<Color> flashColor; private static ConfigEntry<Color> unFlashColor; private static ConfigEntry<bool> ignoreShipContainers; private static ConfigEntry<bool> ignoreWagonContainers; private static ConfigEntry<bool> ignoreWoodChests; private static ConfigEntry<bool> ignorePrivateChests; private static ConfigEntry<bool> ignoreBlackMetalChests; private static ConfigEntry<bool> ignoreReinforcedChests; public static Color FlashColor => flashColor.Value; public static Color UnFlashColor => unFlashColor.Value; public static void Init(ConfigFile config) { //IL_018e: 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) File = config; Flags = new FeatureFlags(); modEnabled = Bind(config, "1 - Server", "Enable mod", defaultValue: true, "Enable this mod."); maxRange = Bind(config, "1 - Server", "Maximum range", 10f, "The maximum distance at which storage is used. Measured from the player, or from the station for the fuel, ore and cooking features. 0 or less means unlimited, matching CraftFromContainers."); enableChests = Bind(config, "1 - Server", "Enable chests", defaultValue: true, "Pull from vanilla chests."); enableDrawers = Bind(config, "1 - Server", "Enable drawers", defaultValue: true, "Pull from KG Item Drawers drawers."); leaveOne = Bind(config, "1 - Server", "Leave one", defaultValue: false, "Always leave the last item of each type in the source."); pullByDistance = Bind(config, "1 - Server", "Pull by distance", defaultValue: false, "Pull from the nearest storage first."); pullFuel = Bind(config, "1 - Server", "Pull fuel", defaultValue: true, "Let the mod feed fuel to fireplaces and smelters."); pullOre = Bind(config, "1 - Server", "Pull ore", defaultValue: true, "Let the mod feed ore to smelters."); pullCooking = Bind(config, "1 - Server", "Pull cooking", defaultValue: true, "Let the mod feed cooking ingredients to cooking stations."); fuelDisallowTypes = Bind(config, "1 - Server", "Fuel disallow types", "RoundLog,FineWood", "Item prefab names that may not be used as fuel, comma-separated."); oreDisallowTypes = Bind(config, "1 - Server", "Ore disallow types", "RoundLog,FineWood", "Item prefab names that may not be used as ore, comma-separated."); resourceString = Bind(config, "1 - Server", "Resource string", "{0}/{1}", "Format used for the required/available resource readout; {0} is available and {1} is required. Empty leaves it at the default."); pulledMessage = Bind(config, "1 - Server", "Pulled message", "Pulled items to inventory", "HUD message shown after pulling items into the inventory. Empty disables it."); flashColor = Bind<Color>(config, "1 - Server", "Flash color", Color.yellow, "Colour the resource readout flashes when the materials come from storage."); unFlashColor = Bind<Color>(config, "1 - Server", "Unflash color", Color.white, "Colour the resource readout alternates with while flashing. Set both colours equal to disable the flash."); ignoreShipContainers = Bind(config, "1 - Server", "Ignore ship containers", defaultValue: false, "Ignore chests aboard ships."); ignoreWagonContainers = Bind(config, "1 - Server", "Ignore wagon containers", defaultValue: false, "Ignore chests aboard carts."); ignoreWoodChests = Bind(config, "1 - Server", "Ignore wood chests", defaultValue: false, "Ignore wood chests."); ignorePrivateChests = Bind(config, "1 - Server", "Ignore private chests", defaultValue: false, "Ignore private chests."); ignoreBlackMetalChests = Bind(config, "1 - Server", "Ignore black metal chests", defaultValue: false, "Ignore black metal chests."); ignoreReinforcedChests = Bind(config, "1 - Server", "Ignore reinforced chests", defaultValue: false, "Ignore reinforced chests."); pullItemsKey = Bind(config, "2 - Keys", "Pull items key", "left ctrl", "Held key that pulls a recipe's ingredients into the inventory. Use a Unity key name."); toggleStorageKey = Bind(config, "2 - Keys", "Toggle storage key", "x", "Pressed to toggle a hovered storage on or off. The crosshair decides what it applies to, so a single key is usual and a movement key must never be used: it would toggle whatever you look at while walking. A combination such as \"left shift + e\" also works - every name but the last is held, and the last is pressed. Empty leaves the toggle unbound."); preventModKey = Bind(config, "2 - Keys", "Prevent mod key", "left alt", "Held key that suppresses this mod's pulling. Use a Unity key name."); fillAllModKey = Bind(config, "2 - Keys", "Fill all mod key", "left shift", "Held key that asks storage for a station's whole remaining capacity instead of a single item, taking as much of it as the storage can supply. Use a Unity key name."); switchPrevent = Bind(config, "2 - Keys", "Switch prevent", defaultValue: false, "If true, holding the prevent key enables this mod's pulling instead of suppressing it."); isDebug = Bind(config, "9 - Debug", "Debug", defaultValue: false, "Print debug messages to the log."); SynchronizationManager.OnConfigurationSynchronized += RefreshFlags; } public static void ReadInto(FeatureFlags flags) { flags.ModEnabled = modEnabled.Value; flags.IsDebug = isDebug.Value; flags.MaxRange = maxRange.Value; flags.PullItemsKey = pullItemsKey.Value; flags.ToggleStorageKey = toggleStorageKey.Value; flags.PreventModKey = preventModKey.Value; flags.FillAllModKey = fillAllModKey.Value; flags.SwitchPrevent = switchPrevent.Value; flags.EnableChests = enableChests.Value; flags.EnableDrawers = enableDrawers.Value; flags.LeaveOne = leaveOne.Value; flags.PullByDistance = pullByDistance.Value; flags.PullFuel = pullFuel.Value; flags.PullOre = pullOre.Value; flags.PullCooking = pullCooking.Value; flags.FuelDisallowTypes = fuelDisallowTypes.Value; flags.OreDisallowTypes = oreDisallowTypes.Value; flags.ResourceString = resourceString.Value; flags.PulledMessage = pulledMessage.Value; flags.IgnoreShipContainers = ignoreShipContainers.Value; flags.IgnoreWagonContainers = ignoreWagonContainers.Value; flags.IgnoreWoodChests = ignoreWoodChests.Value; flags.IgnorePrivateChests = ignorePrivateChests.Value; flags.IgnoreBlackMetalChests = ignoreBlackMetalChests.Value; flags.IgnoreReinforcedChests = ignoreReinforcedChests.Value; } private static void RefreshFlags(object sender, ConfigurationSynchronizationEventArgs e) { ReadInto(Flags); } private static ConfigEntry<T> Bind<T>(ConfigFile config, string section, string key, T defaultValue, string description) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown ConfigEntry<T> obj = config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true } })); obj.SettingChanged += OnSettingChanged; return obj; } private static void OnSettingChanged(object sender, EventArgs e) { FeatureFlags flags = Flags; if (flags != null) { ReadInto(flags); } } } public static class MultiUserChestBridge { private static readonly ManualLogSource Log = Logger.CreateLogSource("CraftFromDrawersAndChests"); public static int RemoveFromChest(Container chest, ItemData item, Inventory destination, int amount) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)chest == (Object)null || item == null || item.m_shared == null || destination == null || amount <= 0) { return 0; } ZNetView nview = chest.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return 0; } if (!nview.HasOwner()) { nview.ClaimOwnership(); } string name = item.m_shared.m_name; int num = CountMatching(destination, name); Vector2i val = default(Vector2i); ((Vector2i)(ref val))..ctor(-1, -1); ZDOID val2 = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Character)Player.m_localPlayer).GetZDOID() : ZDOID.None); RequestChestRemove val3 = ContainerHandler.RemoveItemFromChest(chest, item, destination, val, val2, amount, (ItemData)null); int delivered = CountMatching(destination, name) - num; bool flag = ChestTake.WasRouted(val3 == null, val3?.dragAmount ?? 0); if (flag) { ManualLogSource log = Log; string[] obj = new string[7] { "Chest is managed by another player; routed a request for ", null, null, null, null, null, null }; int dragAmount = val3.dragAmount; obj[1] = dragAmount.ToString(); obj[2] = "x '"; obj[3] = PrefabName(item); obj[4] = "'. The owner applies it asynchronously, so the moved count cannot be reported here yet; returning "; obj[5] = (-1).ToString(); obj[6] = " (deferred)."; log.LogWarning((object)string.Concat(obj)); } else if (ChestTake.DeliveredNothing(flag, delivered)) { Log.LogWarning((object)("MultiUserChest delivered nothing into the destination for '" + PrefabName(item) + "' (the chest may be empty or the destination full).")); } return ChestTake.Result(flag, delivered); } public static bool CanRemoveSynchronously(Container chest) { ZNetView val = (((Object)(object)chest != (Object)null) ? chest.m_nview : null); if ((Object)(object)val == (Object)null || !val.IsValid()) { return false; } return ChestTake.CanServeSynchronously(val.HasOwner(), val.IsOwner()); } private static int CountMatching(Inventory inventory, string sharedName) { if (inventory == null || string.IsNullOrEmpty(sharedName)) { return 0; } List<ItemData> allItems = inventory.GetAllItems(); if (allItems == null) { return 0; } int num = 0; for (int i = 0; i < allItems.Count; i++) { ItemData val = allItems[i]; if (val != null && val.m_shared != null && string.Equals(val.m_shared.m_name, sharedName, StringComparison.Ordinal)) { num += val.m_stack; } } return num; } private static string PrefabName(ItemData item) { if (item == null) { return "<null>"; } if ((Object)(object)item.m_dropPrefab != (Object)null) { return ((Object)item.m_dropPrefab).name; } if (item.m_shared == null) { return "<unknown>"; } return item.m_shared.m_name; } } } namespace CraftFromDrawersAndChests.Toggle { public static class StorageToggle { public static readonly int ZdoHash = StringExtensionMethods.GetStableHashCode("CFDAC_NoPull"); public static bool IsOff(ZNetView view) { ZDO val = (((Object)(object)view != (Object)null) ? view.GetZDO() : null); if (val != null) { return val.GetBool(ZdoHash, false); } return false; } public static void Flip(ZNetView view) { if (!((Object)(object)view == (Object)null)) { view.ClaimOwnership(); ZDO zDO = view.GetZDO(); if (zDO != null) { zDO.Set(ZdoHash, !zDO.GetBool(ZdoHash, false)); } } } public static string HoverSuffix(bool off) { return ToggleRules.HoverSuffix(off); } public static string HoverText(ZNetView view) { if (!((Object)(object)view == (Object)null)) { return HoverSuffix(IsOff(view)) + ToggleHint(); } return string.Empty; } public static string ToggleHint() { return ToggleRules.ToggleHint(PluginConfig.Flags?.ToggleStorageKey); } public static bool IsToggleKeyPressed() { string[] array = ToggleRules.SplitKeys(PluginConfig.Flags?.ToggleStorageKey); if (array.Length == 0) { return false; } try { for (int i = 0; i < array.Length - 1; i++) { if (!Input.GetKey(array[i])) { return false; } } return Input.GetKeyDown(array[^1]); } catch { return false; } } } [HarmonyPatch] internal static class TogglePatches { [HarmonyPatch(typeof(Container), "GetHoverText")] [HarmonyPostfix] private static void ContainerGetHoverTextPostfix(Container __instance, ref string __result) { AppendState(VanillaChestSource.ViewOf(__instance), ref __result); } [HarmonyPatch(typeof(Player), "Update")] [HarmonyPostfix] private static void PlayerUpdatePostfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && StorageToggle.IsToggleKeyPressed()) { ZNetView val = HoveredStorageView(); if ((Object)(object)val != (Object)null) { StorageToggle.Flip(val); } } } private static ZNetView HoveredStorageView() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return null; } GameObject hoverObject = ((Humanoid)localPlayer).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return null; } Container componentInParent = hoverObject.GetComponentInParent<Container>(); if ((Object)(object)componentInParent != (Object)null) { return VanillaChestSource.ViewOf(componentInParent); } ZNetView componentInParent2 = hoverObject.GetComponentInParent<ZNetView>(); if (!((Object)(object)componentInParent2 != (Object)null) || !DrawerApi.IsDrawer(componentInParent2)) { return null; } return componentInParent2; } private static void AppendState(ZNetView view, ref string text) { if (!((Object)(object)view == (Object)null)) { text += StorageToggle.HoverText(view); } } } [HarmonyPatch] internal static class DrawerHoverTextPatch { private const string DrawerComponentFullName = "kg_ItemDrawers.DrawerComponent"; [HarmonyPrepare] private static bool Prepare() { return DrawerComponentType() != null; } [HarmonyTargetMethod] private static MethodBase TargetMethod() { Type type = DrawerComponentType(); if (!(type != null)) { return null; } return AccessTools.Method(type, "GetHoverText", (Type[])null, (Type[])null); } [HarmonyPostfix] private static void Postfix(object __instance, ref string __result) { Component val = (Component)((__instance is Component) ? __instance : null); ZNetView val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ZNetView>() : null); if (!((Object)(object)val2 == (Object)null) && DrawerApi.IsDrawer(val2)) { __result += StorageToggle.HoverText(val2); } } private static Type DrawerComponentType() { return AccessTools.TypeByName("kg_ItemDrawers.DrawerComponent"); } } } namespace CraftFromDrawersAndChests.Storage { [HarmonyPatch(typeof(Container))] internal static class ContainerTrackingPatches { private static readonly ManualLogSource Log = Logger.CreateLogSource("CraftFromDrawersAndChests"); private static bool breadcrumbLogged; private const string BuiltPiecePrefix = "piece_"; private const string ContainerNamePrefix = "Container"; private static readonly int CreatorHash = StringExtensionMethods.GetStableHashCode("creator"); private static readonly List<Container> TrackedContainers = new List<Container>(); internal static IReadOnlyList<Container> Tracked => TrackedContainers; internal static void Clear() { TrackedContainers.Clear(); } [HarmonyPatch("Awake")] [HarmonyPostfix] private static void AwakePostfix(Container __instance) { ZNetView val = VanillaChestSource.ViewOf(__instance); bool flag = ShouldTrack(__instance, val); if (!breadcrumbLogged) { breadcrumbLogged = true; Log.LogInfo((object)("CraftFromDrawersAndChests: tracking patch is live; first Container.Awake was '" + (((Object)(object)__instance != (Object)null && (Object)(object)((Component)__instance).gameObject != (Object)null) ? ((Object)__instance).name : "<null>") + "', inventory=" + ((Object)(object)__instance != (Object)null && __instance.GetInventory() != null) + ", zdo=" + ((Object)(object)val != (Object)null && val.GetZDO() != null) + " -> " + (flag ? "tracked" : "rejected"))); } if (flag) { AddUnique(__instance); } } [HarmonyPatch("OnDestroyed")] [HarmonyPostfix] private static void OnDestroyedPostfix(Container __instance) { TrackedContainers.Remove(__instance); } internal static bool ShouldTrack(Container container, ZNetView view) { if ((Object)(object)container == (Object)null || (Object)(object)((Component)container).gameObject == (Object)null) { return false; } if (container.GetInventory() == null) { return false; } if ((Object)(object)view == (Object)null) { return false; } ZDO zDO = view.GetZDO(); if (zDO == null) { return false; } string name = ((Object)container).name; if (name != null && (name.StartsWith("piece_", StringComparison.Ordinal) || name.StartsWith("Container", StringComparison.Ordinal))) { return true; } return zDO.GetLong(CreatorHash, 0L) != 0; } private static void AddUnique(Container container) { if (!TrackedContainers.Contains(container)) { TrackedContainers.Add(container); } } } public readonly struct DrawerHandle { public readonly ZNetView View; public readonly string Prefab; public readonly int Amount; public readonly int Quality; public DrawerHandle(ZNetView view, string prefab, int amount, int quality) { View = view; Prefab = prefab; Amount = amount; Quality = quality; } } public static class DrawerApi { public const string PrefabKey = "Prefab"; public const string AmountKey = "Amount"; public const string QualityKey = "Quality"; public const string DrawerComponentTypeName = "DrawerComponent"; public const string ForceRemoveRpcName = "ForceRemove"; public const string AddItemRequestRpcName = "AddItem_Request"; private static readonly ManualLogSource Log = Logger.CreateLogSource("CraftFromDrawersAndChests"); private static readonly DrawerHandle[] NoHandles = new DrawerHandle[0]; private static readonly DrawerContract Contract = DrawerContract.Resolve(Type.GetType); private static readonly Type DrawerViewType = ResolveDrawerViewType(); private static readonly FieldInfo DrawerViewField = FindZNetViewField(DrawerViewType); private static readonly FieldInfo DrawerPrefabField = FindField(DrawerViewType, "Prefab"); private static readonly FieldInfo DrawerAmountField = FindField(DrawerViewType, "Amount"); private static readonly FieldInfo DrawerQualityField = FindField(DrawerViewType, "Quality"); public static bool IsAvailable => Contract.IsAvailable; public static IReadOnlyList<DrawerHandle> AllInRange(Vec3 origin, float range) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (!Contract.IsAvailable) { return NoHandles; } try { if (Contract.Kind == DrawerContractKind.ItemDrawersApi) { return FromItemDrawersApi(new Vector3(origin.X, origin.Y, origin.Z), range); } return FromClientSideV2(origin, range); } catch (Exception ex) { Log.LogWarning((object)("Item Drawers query failed; treating as no drawers in range. " + ex)); return NoHandles; } } public static bool IsDrawer(ZNetView view) { if ((Object)(object)view == (Object)null) { return false; } GameObject gameObject = ((Component)view).gameObject; if ((Object)(object)gameObject != (Object)null) { return (Object)(object)gameObject.GetComponent("DrawerComponent") != (Object)null; } return false; } private static IReadOnlyList<DrawerHandle> FromItemDrawersApi(Vector3 position, float range) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!(Contract.RangeQuery.Invoke(null, new object[2] { position, range }) is IEnumerable enumerable)) { return NoHandles; } List<DrawerHandle> list = new List<DrawerHandle>(); foreach (object item in enumerable) { if (item != null) { ZNetView val = ReadView(item); if (!((Object)(object)val == (Object)null)) { list.Add(new DrawerHandle(val, ReadString(DrawerPrefabField, item), ReadInt(DrawerAmountField, item), ReadInt(DrawerQualityField, item))); } } } return list; } private static IReadOnlyList<DrawerHandle> FromClientSideV2(Vec3 origin, float range) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (!(Contract.AllDrawersQuery.Invoke(null, null) is IEnumerable enumerable)) { return NoHandles; } List<DrawerHandle> list = new List<DrawerHandle>(); foreach (object item in enumerable) { ZNetView val = (ZNetView)((item is ZNetView) ? item : null); if ((Object)(object)val == (Object)null || !val.IsValid()) { continue; } Vector3 position = ((Component)val).transform.position; if (Eligibility.IsWithinRange(range, new Vec3(position.x, position.y, position.z).SqrDistanceTo(origin))) { ZDO zDO = val.GetZDO(); if (zDO != null) { list.Add(new DrawerHandle(val, zDO.GetString("Prefab", string.Empty), zDO.GetInt("Amount", 0), zDO.GetInt("Quality", 1))); } } } return list; } private static Type ResolveDrawerViewType() { MethodInfo rangeQuery = Contract.RangeQuery; if (rangeQuery == null) { return null; } Type returnType = rangeQuery.ReturnType; if (returnType.IsArray) { return returnType.GetElementType(); } if (returnType.IsGenericType) { return returnType.GetGenericArguments()[0]; } return null; } private static FieldInfo FindZNetViewField(Type drawerViewType) { if (drawerViewType == null) { return null; } FieldInfo[] fields = drawerViewType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < fields.Length; i++) { if (fields[i].FieldType == typeof(ZNetView)) { return fields[i]; } } return null; } private static FieldInfo FindField(Type type, string name) { if (!(type != null)) { return null; } return type.GetField(name); } private static ZNetView ReadView(object drawer) { if (!(DrawerViewField != null)) { return null; } object? value = DrawerViewField.GetValue(drawer); return (ZNetView)((value is ZNetView) ? value : null); } private static string ReadString(FieldInfo field, object instance) { if (!(field != null)) { return null; } return field.GetValue(instance) as string; } private static int ReadInt(FieldInfo field, object instance) { object obj = ((field != null) ? field.GetValue(instance) : null); if (!(obj is int)) { return 0; } return (int)obj; } } public sealed class DrawerSource : IStorageSource { private static readonly int NoPullHash = StringExtensionMethods.GetStableHashCode("CFDAC_NoPull"); private static readonly ManualLogSource Log = Logger.CreateLogSource("CraftFromDrawersAndChests"); private readonly DrawerHandle _handle; public StorageKind Kind => StorageKind.Drawer; public Vec3 Position { get { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)_handle.View).transform.position; return new Vec3(position.x, position.y, position.z); } } public string ObjectName { get { GameObject val = (((Object)(object)_handle.View != (Object)null) ? ((Component)_handle.View).gameObject : null); if (!((Object)(object)val != (Object)null)) { return null; } return ((Object)val).name; } } public bool ToggledOff { get { ZDO val = (((Object)(object)_handle.View != (Object)null) ? _handle.View.GetZDO() : null); if (val != null) { return val.GetBool(NoPullHash, false); } return false; } } public bool IsValid { get { if ((Object)(object)_handle.View != (Object)null) { return _handle.View.IsValid(); } return false; } } public DrawerSource(DrawerHandle handle) { _handle = handle; } public int Count(string itemName) { if (string.IsNullOrEmpty(itemName) || !IsValid) { return 0; } ZDO zDO = _handle.View.GetZDO(); if (zDO == null) { return 0; } return DrawerStock.CountFor(zDO.GetString("Prefab", string.Empty), zDO.GetInt("Amount", 0), itemName); } public bool TryReadRaw(out string prefab, out int amount, out bool toggledOff) { prefab = null; amount = 0; toggledOff = false; if (!IsValid) { return false; } ZDO zDO = _handle.View.GetZDO(); if (zDO == null) { return false; } prefab = zDO.GetString("Prefab", string.Empty); amount = zDO.GetInt("Amount", 0); toggledOff = zDO.GetBool(NoPullHash, false); return true; } public int Take(string itemName, int amount, Inventory destination) { int num = DrawerTake.AmountToRemove(amount, Count(itemName), destination != null); if (num <= 0) { return 0; } GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(itemName) : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null); ItemData val3 = (((Object)(object)val2 != (Object)null) ? val2.m_itemData : null); if (val3 == null || val3.m_shared == null || val3.m_shared.m_maxStackSize <= 0) { Log.LogWarning((object)("Drawer '" + ObjectName + "' reports prefab '" + itemName + "' that has no usable ItemDrop; taking nothing.")); return 0; } int maxStackSize = val3.m_shared.m_maxStackSize; string name = val3.m_shared.m_name; int quality = CurrentQuality(); int num2 = (byte)Game.m_worldLevel; int num3 = 0; int num4; while ((num4 = DrawerTake.NextChunk(num, num3, maxStackSize)) > 0) { int matchingFreeStackSpace = DrawerTake.MatchingFreeStackSpace(name, quality, num2, DestinationStacks(destination)); int emptySlots = DestinationEmptySlots(destination); if (!DrawerTake.CanDeliver(num4, maxStackSize, matchingFreeStackSpace, emptySlots)) { Log.LogWarning((object)("No room in the destination inventory for the next " + num4 + "x '" + itemName + "'; delivered " + num3 + " of " + num + ".")); break; } _handle.View.ClaimOwnership(); _handle.View.InvokeRPC("ForceRemove", new object[1] { num4 }); ItemData val4 = val3.Clone(); val4.m_dropPrefab = val; val4.m_stack = num4; val4.m_quality = quality; val4.m_worldLevel = (byte)num2; if (!destination.AddItem(val4)) { Log.LogError((object)("A drawer gave up " + num4 + "x '" + itemName + "' but the destination refused them; refunding them to the drawer.")); Refund(itemName, num4, quality); break; } num3 += num4; } return num3; } private int CurrentQuality() { ZDO val = (((Object)(object)_handle.View != (Object)null) ? _handle.View.GetZDO() : null); int num = ((val == null) ? 1 : val.GetInt("Quality", 1)); if (num >= 1) { return num; } return 1; } private static List<DrawerTake.InventoryStack> DestinationStacks(Inventory destination) { List<DrawerTake.InventoryStack> list = new List<DrawerTake.InventoryStack>(); if (destination == null || destination.m_inventory == null) { return list; } for (int i = 0; i < destination.m_inventory.Count; i++) { ItemData val = destination.m_inventory[i]; if (val != null && val.m_shared != null) { list.Add(new DrawerTake.InventoryStack(val.m_shared.m_name, val.m_quality, val.m_worldLevel, val.m_stack, val.m_shared.m_maxStackSize)); } } return list; } private static int DestinationEmptySlots(Inventory destination) { if (destination == null || destination.m_inventory == null) { return 0; } return destination.m_width * destination.m_height - destination.m_inventory.Count; } private void Refund(string prefabName, int amount, int quality) { _handle.View.ClaimOwnership(); _handle.View.InvokeRPC("AddItem_Request", new object[3] { prefabName, amount, quality }); } } public interface IStorageSource { StorageKind Kind { get; } Vec3 Position { get; } string ObjectName { get; } bool ToggledOff { get; } bool IsValid { get; } int Count(string itemName); int Take(string itemName, int amount, Inventory destination); } public static class StorageRegistry { public static IReadOnlyList<IStorageSource> Collect(Vec3 origin, FeatureFlags flags) { List<IStorageSource> list = new List<IStorageSource>(); if (flags == null) { return list; } ChestTypeRules chestRules = ChestTypeRules.FromFlags(flags); CollectChests(origin, flags, chestRules, list); CollectDrawers(origin, flags, chestRules, list); return list; } public static void Clear() { ContainerTrackingPatches.Clear(); } private static void CollectChests(Vec3 origin, FeatureFlags flags, ChestTypeRules chestRules, List<IStorageSource> sources) { IReadOnlyList<Container> tracked = ContainerTrackingPatches.Tracked; for (int i = 0; i < tracked.Count; i++) { Container val = tracked[i]; if (!((Object)(object)val == (Object)null)) { VanillaChestSource vanillaChestSource = new VanillaChestSource(val); if (vanillaChestSource.IsValid && Eligibility.IsEligible(new StorageCandidate(StorageKind.Chest, vanillaChestSource.Position, vanillaChestSource.ObjectName, vanillaChestSource.ParentIsShip, vanillaChestSource.ParentIsWagon, vanillaChestSource.ToggledOff, vanillaChestSource.HasAccess, vanillaChestSource.HasInventory, vanillaChestSource.IsInsidePiece, vanillaChestSource.PassesGuardStone), origin, flags.MaxRange, flags.EnableChests, flags.EnableDrawers, chestRules)) { sources.Add(vanillaChestSource); } } } } private static void CollectDrawers(Vec3 origin, FeatureFlags flags, ChestTypeRules chestRules, List<IStorageSource> sources) { IReadOnlyList<DrawerHandle> readOnlyList = DrawerApi.AllInRange(origin, flags.MaxRange); for (int i = 0; i < readOnlyList.Count; i++) { DrawerSource drawerSource = new DrawerSource(readOnlyList[i]); if (drawerSource.IsValid && Eligibility.IsEligible(new StorageCandidate(StorageKind.Drawer, drawerSource.Position, drawerSource.ObjectName, parentIsShip: false, parentIsWagon: false, drawerSource.ToggledOff, hasAccess: false, hasInventory: false, isInsidePiece: false, passesGuardStone: false), origin, flags.MaxRange, flags.EnableChests, flags.EnableDrawers, chestRules)) { sources.Add(drawerSource); } } } } public sealed class VanillaChestSource : IStorageSource { private static readonly int NoPullHash = StringExtensionMethods.GetStableHashCode("CFDAC_NoPull"); private static readonly MethodInfo CheckAccessMethod = AccessTools.Method(typeof(Container), "CheckAccess", (Type[])null, (Type[])null); private static readonly ManualLogSource Log = Logger.CreateLogSource("CraftFromDrawersAndChests"); private static bool accessWarningLogged; private readonly Container _container; public StorageKind Kind => StorageKind.Chest; public Vec3 Position { get { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)_container).transform.position; return new Vec3(position.x, position.y, position.z); } } public string ObjectName { get { GameObject val = (((Object)(object)_container != (Object)null) ? ((Component)_container).gameObject : null); if (!((Object)(object)val != (Object)null)) { return null; } return ((Object)val).name; } } public bool ToggledOff { get { ZDO zdo = Zdo; if (zdo != null) { return zdo.GetBool(NoPullHash, false); } return false; } } public bool IsValid { get { if ((Object)(object)_container != (Object)null) { return (Object)(object)((Component)_container).gameObject != (Object)null; } return false; } } public bool CanTakeSynchronously => MultiUserChestBridge.CanRemoveSynchronously(_container); internal bool HasInventory { get { if ((Object)(object)_container != (Object)null) { return _container.GetInventory() != null; } return false; } } internal bool HasAccess { get { if ((Object)(object)_container == (Object)null) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } if (CheckAccessMethod == null) { if (!accessWarningLogged) { accessWarningLogged = true; Log.LogWarning((object)"Container.CheckAccess could not be reflected; no chest is treated as accessible."); } return false; } object obj = CheckAccessMethod.Invoke(_container, new object[1] { localPlayer.GetPlayerID() }); if (obj is bool) { return (bool)obj; } return false; } } internal bool ParentIsShip { get { if ((Object)(object)_container == (Object)null) { return false; } Transform parent = ((Component)_container).transform.parent; if ((Object)(object)parent != (Object)null) { return (Object)(object)((Component)parent).GetComponent<Ship>() != (Object)null; } return false; } } internal bool ParentIsWagon { get { if ((Object)(object)_container != (Object)null) { return (Object)(object)_container.m_wagon != (Object)null; } return false; } } internal bool IsInsidePiece { get { if ((Object)(object)_container != (Object)null) { return (Object)(object)((Component)_container).GetComponentInParent<Piece>() != (Object)null; } return false; } } internal bool PassesGuardStone { get { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_container == (Object)null) { return false; } if (_container.m_checkGuardStone) { return PrivateArea.CheckAccess(((Component)_container).transform.position, 0f, false, false); } return true; } } private ZDO Zdo { get { ZNetView val = ViewOf(_container); if (!((Object)(object)val != (Object)null)) { return null; } return val.GetZDO(); } } public VanillaChestSource(Container container) { _container = container; } public int Count(string itemName) { if (string.IsNullOrEmpty(itemName) || !IsValid) { return 0; } Inventory inventory = _container.GetInventory(); if (inventory == null) { return 0; } List<ItemData> allItems = inventory.GetAllItems(); if (allItems == null) { return 0; } int num = 0; for (int i = 0; i < allItems.Count; i++) { ItemData val = allItems[i]; if (val != null && !((Object)(object)val.m_dropPrefab == (Object)null) && string.Equals(((Object)val.m_dropPrefab).name, itemName, StringComparison.Ordinal)) { num += val.m_stack; } } return num; } public int Take(string itemName, int amount, Inventory destination) { if (string.IsNullOrEmpty(itemName) || amount <= 0 || destination == null || !IsValid) { return 0; } Inventory inventory = _container.GetInventory(); if (inventory == null) { return 0; } int i; int num; for (i = 0; i < amount; i += num) { ItemData val = FindLargestStack(inventory, itemName); if (val == null) { break; } num = MultiUserChestBridge.RemoveFromChest(_container, val, destination, amount - i); if (num == -1) { return -1; } if (num <= 0) { break; } } return i; } private static ItemData FindLargestStack(Inventory inventory, string itemName) { List<ItemData> allItems = inventory.GetAllItems(); if (allItems == null) { return null; } ItemData val = null; for (int i = 0; i < allItems.Count; i++) { ItemData val2 = allItems[i]; if (val2 != null && !((Object)(object)val2.m_dropPrefab == (Object)null) && string.Equals(((Object)val2.m_dropPrefab).name, itemName, StringComparison.Ordinal) && (val == null || val2.m_stack > val.m_stack)) { val = val2; } } return val; } internal static ZNetView ViewOf(Container container) { if ((Object)(object)container == (Object)null) { return null; } if ((Object)(object)container.m_rootObjectOverride != (Object)null) { return ((Component)container.m_rootObjectOverride).GetComponent<ZNetView>(); } return ((Component)container).GetComponent<ZNetView>(); } } } namespace CraftFromDrawersAndChests.Pull { public static class PullEngine { private static readonly ManualLogSource Log = Logger.CreateLogSource("CraftFromDrawersAndChests"); private static IReadOnlyList<IStorageSource> cachedSources; private static Vec3 cachedOrigin; private static FeatureFlags cachedFlags; private static int cachedFrame = -1; public static IReadOnlyList<IStorageSource> Collect(Vec3 origin, FeatureFlags flags) { int frameCount = Time.frameCount; if (cachedSources != null && frameCount == cachedFrame && cachedFlags == flags && cachedOrigin.X == origin.X && cachedOrigin.Y == origin.Y && cachedOrigin.Z == origin.Z) { return cachedSources; } cachedSources = StorageRegistry.Collect(origin, flags); cachedOrigin = origin; cachedFlags = flags; cachedFrame = frameCount; return cachedSources; } public static string PrefabName(Requirement requirement) { if (requirement == null || (Object)(object)requirement.m_resItem == (Object)null) { return null; } GameObject gameObject = ((Component)requirement.m_resItem).gameObject; if (!((Object)(object)gameObject != (Object)null)) { return null; } return ((Object)gameObject).name; } public static PullRequest[] BuildShortfallRequests(Requirement[] requirements, int qualityLevel, int multiplier, Inventory inventory) { IReadOnlyDictionary<string, int> held = HeldCounts(requirements, inventory); int value; return BuildRequests(requirements, qualityLevel, multiplier, (string name) => held.TryGetValue(name, out value) ? value : 0, RequestBasis.Shortfall); } public static PullRequest[] BuildFullRequests(Requirement[] requirements, int qualityLevel, int multiplier) { return BuildRequests(requirements, qualityLevel, multiplier, null, RequestBasis.FullCost); } private static IReadOnlyDictionary<string, int> HeldCounts(Requirement[] requirements, Inventory inventory) { Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); if (requirements == null || inventory == null) { return dictionary; } foreach (Requirement val in requirements) { if (val != null) { string text = PrefabName(val); if (!string.IsNullOrEmpty(text) && !dictionary.ContainsKey(text)) { dictionary[text] = CountHeldBySharedName(inventory, SharedNameOf(val)); } } } return dictionary; } public static PullRequest[] BuildRequests(Requirement[] requirements, int qualityLevel, int multiplier, Func<string, int> held, RequestBasis basis) { if (requirements == null) { return Array.Empty<PullRequest>(); } List<PullRequest> list = new List<PullRequest>(requirements.Length); foreach (Requirement val in requirements) { if (val == null) { continue; } string text = PrefabName(val); if (string.IsNullOrEmpty(text)) { continue; } int num = val.GetAmount(qualityLevel) * multiplier; if (num > 0) { int held2 = held?.Invoke(text) ?? 0; int num2 = PullMath.RequestAmount(num, held2, basis); if (num2 > 0) { list.Add(new PullRequest(text, text, num2, val.m_upgraderResource)); } } } return list.ToArray(); } public static PullPlanResult PlanFor(Requirement[] requirements, int qualityLevel, int multiplier, Player player, FeatureFlags flags, out IReadOnlyList<IStorageSource> sources) { return PlanFor(requirements, qualityLevel, multiplier, ((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null, OriginOf(player), flags, AtUpgrader(player), out sources); } public static PullPlanResult PlanFor(Requirement[] requirements, int qualityLevel, int multiplier, Inventory inventory, Vec3 origin, FeatureFlags flags, bool allowUpgraderResources, out IReadOnlyList<IStorageSource> sources) { sources = Collect(origin, flags); return Plan(BuildShortfallRequests(requirements, qualityLevel, multiplier, inventory), sources, origin, flags, allowUpgraderResources); } public static PullPlanResult Plan(IReadOnlyList<PullRequest> requests, IReadOnlyList<IStorageSource> sources, Vec3 origin, FeatureFlags flags, bool allowUpgraderResources, bool fillUpTo = false) { IReadOnlyList<StorageStock> stocks = BuildStocks(sources, requests, origin); bool leaveOne = flags?.LeaveOne ?? false; bool byDistance = flags?.PullByDistance ?? false; return PullPlan.Plan(requests, stocks, leaveOne, byDistance, allowUpgraderResources, fillUpTo); } public static bool Execute(PullPlanResult plan, IReadOnlyList<IStorageSource> sources, Player player) { return Execute(plan, sources, ((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null); } public static bool Execute(PullPlanResult plan, IReadOnlyList<IStorageSource> sources, Inventory destination) { if (plan == null || !plan.Feasible) { return false; } if (destination == null) { return false; } IReadOnlyDictionary<string, IStorageSource> byId = ById(sources); return PullRunner.Run(plan.Lines, delegate(PullLine line, int amount) { if (!byId.TryGetValue(line.SourceId, out var value) || value == null || !value.IsValid) { ManualLogSource log = Log; string[] obj = new string[5] { "Pull: the source for ", null, null, null, null }; int amount2 = line.Amount; obj[1] = amount2.ToString(); obj[2] = "x '"; obj[3] = line.ItemName; obj[4] = "' is gone before it could be taken; treating it as exhausted."; log.LogWarning((object)string.Concat(obj)); return 0; } return value.Take(line.ItemName, amount, destination); }, delegate(string message) { Log.LogWarning((object)message); }); } public static bool PullToPlayer(Requirement[] requirements, int qualityLevel, int multiplier, Player player, FeatureFlags flags, out bool requested) { return PullToInventoryCore(requirements, qualityLevel, multiplier, ((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null, OriginOf(player), flags, AtUpgrader(player), RequestBasis.FullCost, fillUpTo: true, out requested); } public static bool PullToInventory(Requirement[] requirements, int qualityLevel, int multiplier, Inventory destination, Vec3 origin, FeatureFlags flags, bool allowUpgraderResources, out bool requested) { return PullToInventoryCore(requirements, qualityLevel, multiplier, destination, origin, flags, allowUpgraderResources, RequestBasis.Shortfall, fillUpTo: false, out requested); } public static bool PullToInventoryFill(Requirement[] requirements, int qualityLevel, int multiplier, Inventory destination, Vec3 origin, FeatureFlags flags, bool allowUpgraderResources, out bool requested) { return PullToInventoryCore(requirements, qualityLevel, multiplier, destination, origin, flags, allowUpgraderResources, RequestBasis.Shortfall, fillUpTo: true, out requested); } private static bool PullToInventoryCore(Requirement[] requirements, int qualityLevel, int multiplier, Inventory destination, Vec3 origin, FeatureFlags flags, bool allowUpgraderResources, RequestBasis basis, bool fillUpTo, out bool requested) { requested = false; if (destination == null || flags == null) { return false; } IReadOnlyList<IStorageSource> sources = Collect(origin, flags); PullRequest[] requests = ((basis == RequestBasis.FullCost) ? BuildFullRequests(requirements, qualityLevel, multiplier) : BuildShortfallRequests(requirements, qualityLevel, multiplier, destination)); if (fillUpTo) { requests = CapToDestination(requests, requirements, destination); } PullPlanResult pullPlanResult = Plan(requests, sources, origin, flags, allowUpgraderResources, fillUpTo); if (pullPlanResult.Feasible && pullPlanResult.Lines.Count == 0) { return true; } requested = true; if (!pullPlanResult.Feasible) { return false; } if (!DestinationAllows(destination, requirements, pullPlanResult, null)) { return false; } return Execute(pullPlanResult, sources, destination); } private static PullRequest[] CapToDestination(PullRequest[] requests, Requirement[] requirements, Inventory destination) { if (requests == null || requests.Length == 0) { return requests; } List<DrawerTake.InventoryStack> list = null; int emptySlots = 0; PullRequest[] array = new PullRequest[requests.Length]; for (int i = 0; i < requests.Length; i++) { PullRequest pullRequest = requests[i]; ItemData val = ItemDataOf(FindRequirement(requirements, pullRequest.ItemName)); if (val == null || val.m_shared == null || val.m_shared.m_maxStackSize <= 0) { array[i] = pullRequest; continue; } if (list == null) { list = InventoryStacks(destination); emptySlots = DestinationEmptySlots(destination); } DestinationCapacity.Delivery delivery = new DestinationCapacity.Delivery(val.m_shared.m_name, val.m_quality, val.m_worldLevel, pullRequest.Amount, val.m_shared.m_maxStackSize); int num = DestinationCapacity.AcceptableAmount(list, emptySlots, delivery, pullRequest.Amount); array[i] = ((num >= pullRequest.Amount) ? pullRequest : new PullRequest(pullRequest.Prefab, pullRequest.ItemName, num, pullRequest.IsUpgraderResource)); } return array; } public static bool DestinationAllows(Player player, Requirement[] requirements, PullPlanResult plan, DestinationCapacity.Delivery? reserved) { return DestinationAllows(((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null, requirements, plan, reserved); } public static bool DestinationAllows(Inventory inventory, Requirement[] requirements, PullPlanResult plan, DestinationCapacity.Delivery? reserved) { if (plan == null) { return false; } if (inventory == null) { return false; } List<DestinationCapacity.Delivery> list = new List<DestinationCapacity.Delivery>(plan.Lines.Count + 1); if (reserved.HasValue) { list.Add(reserved.Value); } for (int i = 0; i < plan.Lines.Count; i++) { PullLine pullLine = plan.Lines[i]; if (pullLine.Amount > 0) { ItemData val = ItemDataOf(FindRequirement(requirements, pullLine.ItemName)); if (val == null || val.m_shared == null || val.m_shared.m_maxStackSize <= 0) { return false; } list.Add(new DestinationCapacity.Delivery(val.m_shared.m_name, val.m_quality, val.m_worldLevel, pullLine.Amount, val.m_shared.m_maxStackSize)); } } return DestinationCapacity.CanAccept(InventoryStacks(inventory), DestinationEmptySlots(inventory), list); } private static Requirement FindRequirement(Requirement[] resources, string prefab) { if (resources == null || string.IsNullOrEmpty(prefab)) { return null; } foreach (Requirement val in resources) { if (val != null && string.Equals(PrefabName(val), prefab, StringComparison.Ordinal)) { return val; } } return null; } private static ItemData ItemDataOf(Requirement requirement) { if (requirement == null || (Object)(object)requirement.m_resItem == (Object)null) { return null; } return requirement.m_resItem.m_itemData; } public static string SharedNameOf(Requirement requirement) { ItemData val = ItemDataOf(requirement); if (val == null || val.m_shared == null) { return null; } return val.m_shared.m_name; } private static List<DrawerTake.InventoryStack> InventoryStacks(Inventory inventory) { List<DrawerTake.InventoryStack> list = new List<DrawerTake.InventoryStack>(); if (inventory == null || inventory.m_inventory == null) { return list; } for (int i = 0; i < inventory.m_inventory.Count; i++) { ItemData val = inventory.m_inventory[i]; if (val != null && val.m_shared != null) { list.Add(new DrawerTake.InventoryStack(val.m_shared.m_name, val.m_quality, val.m_worldLevel, val.m_stack, val.m_shared.m_maxStackSize)); } } return list; } private static int DestinationEmptySlots(Inventory inventory) { if (inventory == null || inventory.m_inventory == null) { return 0; } return inventory.m_width * inventory.m_height - inventory.m_inventory.Count; } public static Vec3 OriginOf(Player player) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return new Vec3(0f, 0f, 0f); } Vector3 position = ((Component)player).transform.position; return new Vec3(position.x, position.y, position.z); } public static bool AtUpgrader(Player player) { if ((Object)(object)player == (Object)null) { return false; } CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation != (Object)null) { return currentCraftingStation.m_upgrader; } return false; } public static int CountHeldBySharedName(Inventory inventory, string sharedName) { if (inventory == null || string.IsNullOrEmpty(sharedName)) { return 0; } return inventory.CountItems(sharedName, -1, false); } private static IReadOnlyList<StorageStock> BuildStocks(IReadOnlyList<IStorageSource> sources, IReadOnlyList<PullRequest> requests, Vec3 origin) { List<StorageStock> list = new List<StorageStock>(); if (sources == null || sources.Count == 0 || requests == null || requests.Count == 0) { return list; } string[] array = DistinctNames(requests); if (array.Length == 0) { return list; } for (int i = 0; i < sources.Count; i++) { IStorageSource storageSource = sources[i]; if (storageSource == null || !storageSource.IsValid || !CanServeSynchronously(storageSource)) { continue; } float distance = storageSource.Position.DistanceTo(origin); string sourceId = SourceId(i); for (int j = 0; j < array.Length; j++) { int num = storageSource.Count(array[j]); if (num > 0) { list.Add(new StorageStock(sourceId, array[j], num, distance)); } } } return list; } public static bool CanServeSynchronously(IStorageSource source) { if (source is VanillaChestSource vanillaChestSource) { return vanillaChestSource.CanTakeSynchronously; } return true; } private static string[] DistinctNames(IReadOnlyList<PullRequest> requests) { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); List<string> list = new List<string>(); for (int i = 0; i < requests.Count; i++) { string itemName = requests[i].ItemName; if (!string.IsNullOrEmpty(itemName) && hashSet.Add(itemName)) { list.Add(itemName); } } return list.ToArray(); } private static string SourceId(int index) { return index.ToString(CultureInfo.InvariantCulture); } private static IReadOnlyDictionary<string, IStorageSource> ById(IReadOnlyList<IStorageSource> sources) { Dictionary<string, IStorageSource> dictionary = new Dictionary<string, IStorageSource>(StringComparer.Ordinal); if (sources != null) { for (int i = 0; i < sources.Count; i++) { dictionary[SourceId(i)] = sources[i]; } } return dictionary; } } } namespace CraftFromDrawersAndChests.Patches { [HarmonyPatch] internal static class CraftPatches { private static readonly ManualLogSource Log = Logger.CreateLogSource("CraftFromDrawersAndChests"); [HarmonyPatch(typeof(Player), "HaveRequirementItems")] [HarmonyPostfix] private static void HaveRequirementItemsPostfix(Player __instance, Recipe piece, bool discover, int qualityLevel, int amount, ref bool __result) { if (!(__result || discover) && !((Object)(object)piece == (Object)null) && !((Object)(object)__instance == (Object)null) && Enabled() && PullKeyPatches.AllowByKey() && !piece.m_requireOnlyOneIngredient) { __result = CanReach(__instance, piece.m_resources, qualityLevel, amount, CraftedOutput(piece, qualityLevel, amount)); } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Piece), typeof(RequirementMode) })] [HarmonyPostfix] private static void HaveRequirementsPiecePostfix(Player __instance, Piece piece, RequirementMode mode, ref bool __result) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Invalid comparison between Unknown and I4 if (!__result && !((Object)(object)piece == (Object)null) && !((Object)(object)__instance == (Object)null) && Enabled() && PullKeyPatches.AllowByKey() && ((int)mode == 0 || (int)mode == 2) && StationAndDlcAllow(__instance, piece, mode)) { __result = (((int)mode == 2) ? HasAnyOfEach(__instance, piece.m_resources) : CanReach(__instance, piece.m_resources, 0, 1, null)); } } [HarmonyPatch(typeof(Player), "ConsumeResources")] [HarmonyPrefix] private static bool ConsumeResourcesPrefix(Player __instance, Requirement[] requirements, int qualityLevel, int multiplier) { if ((Object)(object)__instance == (Object)null || requirements == null || !Enabled()) { return true; } if (!PullKeyPatches.AllowByKey()) { return true; } BuildPlan(__instance, requirements, qualityLevel, multiplier, out var sources, out var plan); if (!plan.Feasible) { Log.LogWarning((object)("Not consuming: the materials are no longer within range (" + string.Join(", ", plan.MissingItemNames) + ").")); return false; } if (plan.Lines.Count > 0 && !PullEngine.Execute(plan, sources, __instance)) { Log.LogWarning((object)"Not consuming: a storage pull could not be completed, so the materials already moved are left in the inventory rather than eaten."); return false; } return true; } private static bool Enabled() { return PluginConfig.Flags?.ModEnabled ?? false; } private static bool CanReach(Player player, Requirement[] resources, int qualityLevel, int multiplier, DestinationCapacity.Delivery? reserved) { BuildPlan(player, resources, qualityLevel, multiplier, out var _, out var plan); if (plan.Feasible) { return DestinationAllows(player, resources, plan, reserved); } return false; } private static void BuildPlan(Player player, Requirement[] resources, int qualityLevel, int multiplier, out IReadOnlyList<IStorageSource> sources, out PullPlanResult plan) { plan = PullEngine.PlanFor(resources, qualityLevel, multiplier, player, PluginConfig.Flags, out sources); } private static bool DestinationAllows(Player player, Requirement[] resources, PullPlanResult plan, DestinationCapacity.Delivery? reserved) { return PullEngine.DestinationAllows(player, resources, plan, reserved); } private static DestinationCapacity.Delivery? CraftedOutput(Recipe piece, int qualityLevel, int amount) { if ((Object)(object)piece == (Object)null || (Object)(object)piece.m_item == (Object)null) { return null; } ItemData itemData = piece.m_item.m_itemData; if (itemData == null || itemData.m_shared == null || itemData.m_shared.m_maxStackSize <= 0) { return null; } int num = default(int); ItemData val = default(ItemData); int amount2 = piece.GetAmount(qualityLevel, ref num, ref val, amount); bool isUpgrade = DestinationCapacity.IsUpgrade(qualityLevel); InventoryGui instance = InventoryGui.instance; int perRollBonus = (((Object)(object)instance != (Object)null) ? instance.m_craftBonusAmount : 0); int num2 = DestinationCapacity.ReservedOutputAmount(amount2, perRollBonus, amount, isUpgrade); if (num2 <= 0) { return null; } return new DestinationCapacity.Delivery(itemData.m_shared.m_name, qualityLevel, (byte)Game.m_worldLevel, num2, itemData.m_shared.m_maxStackSize); } private static bool HasAnyOfEach(Player player, Requirement[] resources) { if (resources == null) { return true; } FeatureFlags flags = PluginConfig.Flags; IReadOnlyList<IStorageSource> sources = PullEngine.Collect(PullEngine.OriginOf(player), flags); Inventory inventory = ((Humanoid)player).GetInventory(); foreach (Requirement val in resources) { if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0) { string text = PullEngine.PrefabName(val); if (!string.IsNullOrEmpty(text) && PullEngine.CountHeldBySharedName(inventory, PullEngine.SharedNameOf(val)) <= 0 && !AnySourceHolds(sources, text)) { return false; } } } return true; } private static bool AnySourceHolds(IReadOnlyList<IStorageSource> sources, string prefab) { for (int i = 0; i < sources.Count; i++) { IStorageSource storageSource = sources[i]; if (storageSource != null && storageSource.IsValid && PullEngine.CanServeSynchronously(storageSource) && storageSource.Count(prefab) > 0) { return true; } } return false; } private static bool StationAndDlcAllow(Player player, Piece piece, RequirementMode mode) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0041: Unknown result type (might be due to invalid IL or missing references) CraftingStation craftingStation = piece.m_craftingStation; if ((Object)(object)craftingStation != (Object)null) { if ((int)mode == 2 || (int)mode == 1) { if (player.m_knownStations == null || !player.m_knownStations.ContainsKey(craftingStation.m_name)) { return false; } } else if ((Object)(object)CraftingStation.HaveBuildStationInRange(craftingStation.m_name, ((Component)player).transform.position) == (Object)null && !ZoneSystem.instance.GetGlobalKey((GlobalKeys)27)) { return false; } } string dlc = piece.m_dlc; if (!string.IsNullOrEmpty(dlc) && (Object)(object)DLCMan.instance != (Object)null && !DLCMan.instance.IsDLCInstalled(dlc)) { return false; } return true; } } [HarmonyPatch] internal static class PullKeyPatches { private static readonly ManualLogSource Log = Logger.CreateLogSource("CraftFromDrawersAndChests"); private const string ResAmountChild = "res_amount"; private static bool lastAllowed = true; private static readonly Dictionary<string, string> readoutLogged = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly FieldInfo PlacementStatusField = AccessTools.Field(typeof(Player), "m_placementStatus"); [HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")] [HarmonyPrefix] private static bool OnCraftPressedPrefix(RecipeDataPair ___m_selectedRecipe, ItemData ___m_craftUpgradeItem, bool ___m_multiCrafting, int ___m_multiCraftAmount) { FeatureFlags flags = PluginConfig.Flags; if (flags == null || !flags.ModEnabled) { return true; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return true; } Recipe recipe = ((RecipeDataPair)(ref ___m_selectedRecipe)).Recipe; if ((Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null) { return true; } if (!PullKeyRules.ShouldPull(KeyHeld(flags.PullItemsKey), PreventKeyHeld(flags), flags.SwitchPrevent)) { return true; } int num = ((___m_craftUpgradeItem == null) ? 1 : (___m_craftUpgradeItem.m_quality + 1)); if (num > recipe.m_item.m_itemData.m_shared.m_maxQuality) { return true; } int multiplier = ((!___m_multiCrafting || ___m_multiCraftAmount <= 0) ? 1 : ___m_multiCraftAmount); if (!PullEngine.PullToPlayer(recipe.m_resources, num, multiplier, localPlayer, flags, out var requested)) { ShowMessage(localPlayer, FailureReason(localPlayer, recipe.m_resources, num, multiplier, flags)); return false; } if (!requested) { string text = MissingReason(localPlayer, recipe.m_resources, num, multiplier, flags); if (text != null) { ShowMessage(localPlayer, text); } return false; } ShowMessage(localPlayer, flags.PulledMessage); Debug(flags, "Pull: pulled the ingredients for '" + recipe.m_item.m_itemData.m_shared.m_name + "'."); return false; } [HarmonyPatch(typeof(Player), "UpdatePlacement")] [HarmonyPrefix] private static bool UpdatePlacementPrefix(Player __instance, bool takeInput, PieceTable ___m_buildPieces, GameObject ___m_placementGhost) { FeatureFlags flags = PluginConfig.Flags; if (flags == null || (Object)(object)__instance == (Object)null || __instance != Player.m_localPlayer) { return true; } if (!BuildPullRules.ShouldIntercept(flags.ModEnabled, KeyHeld(flags.PullItemsKey), PreventKeyHeld(flags), flags.SwitchPrevent, ((Character)__instance).InPlaceMode(), takeInput, Hud.IsPieceSelectionVisible())) { return true; } if (!ZInput.GetButtonDown("Attack") && !ZInput.GetButtonDown("JoyPlace")) { return true; } Piece val = (((Object)(object)___m_buildPieces != (Object)null) ? ___m_buildPieces.GetSelectedPiece() : null); switch (BuildPullRules.Decide((Object)(object)val != (Object)null, (Object)(object)val != (Object)null && val.m_repairPiece, (Object)(object)___m_placementGhost != (Object)null, BuildPullRules.IsPlacementValid(PlacementStatus(__instance)))) { case BuildPullDecision.LetVanillaHandle: return true; case BuildPullDecision.RefuseWithoutPull: return false; default: { if (!PullEngine.PullToPlayer(val.m_resources, 0, 1, __instance, flags, out var requested)) { ShowMessage(__instance, FailureReason(__instance, val.m_resources, 0, 1, flags)); return false; } if (!requested) { string text = MissingReason(__instance, val.m_resources, 0, 1, flags); if (text != null) { ShowMessage(__instance, text); } return false; } ShowMessage(__instance, flags.PulledMessage); Debug(flags, "Pull: pulled the materials for building piece '" + ((Object)val).name + "'."); return false; } } } [HarmonyPatch(typeof(InventoryGui), "SetupRequirement")] [HarmonyPostfix] private static void SetupRequirementPostfix(Transform elementRoot, Requirement req, Player player, int quality, int craftMultiplier) { //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) FeatureFlags flags = PluginConfig.Flags; if (flags == null || !flags.ModEnabled || !PullKeyRules.AllowByKey(flags.SwitchPrevent, PreventKeyHeld(flags)) || req == null || (Object)(object)req.m_resItem == (Object)null || (Object)(object)elementRoot == (Object)null || (Object)(object)player == (Object)null) { return; } string text = PullEngine.PrefabName(req); string text2 = PullEngine.SharedNameOf(req); if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2)) { return; } int num = req.GetAmount(quality) * craftMultiplier; if (num <= 0) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return; } int num2 = PullEngine.CountHeldBySharedName(inventory, text2); int num3 = 0; IReadOnlyList<IStorageSource> readOnlyList = null; bool flag = num2 < num; if (flag) { readOnlyList = PullEngine.Collect(PullEngine.OriginOf(player), flags); for (int i = 0; i < readOnlyList.Count; i++) { IStorageSource storageSource = readOnlyList[i]; if (storageSource != null && storageSource.IsValid && PullEngine.CanServeSynchronously(storageSource)) { num3 += storageSource.Count(text); } } } int num4 = num2 + num3; LogReadout(flags, text, text2, num, num2, num3, readOnlyList, player); Transform val = elementRoot.Find("res_amount"); if ((Object)(object)val == (Object)null) { return; } TMP_Text component = ((Component)val).GetComponent<TMP_Text>(); if (!((Object)(object)component == (Object)null)) { if (flag && num4 >= num) { ((Graphic)component).color = ((Mathf.Sin(Time.time * 10f) > 0f) ? PluginConfig.FlashColor : PluginConfig.UnFlashColor); } component.text = PullKeyRules.ResourceAmountText(flags.ResourceString, num4, num); } } private static void LogReadout(FeatureFlags flags, string prefab, string sharedName, int amount, int held, int fromStorage, IReadOnlyList<IStorageSource> sources, Player player) { if (flags == null || !flags.IsDebug) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Readout '").Append(prefab).Append("' (") .Append(sharedName) .Append("): held ") .Append(held) .Append(" + storage ") .Append(fromStorage) .Append(" of ") .Append(amount) .Append(" required"); if (held < amount && fromStorage == 0 && sources != null) { Vec3 other = PullEngine.OriginOf(player); stringBuilder.Append(" | nothing holds it; ").Append(sources.Count).Append(" source(s) in range:"); for (int i = 0; i < sources.Count; i++) { IStorageSource storageSource = sources[i]; if (storageSource != null && storageSource.IsValid) { stringBuilder.Append(" [").Append(storageSource.Kind).Append(' ') .Append(storageSource.ObjectName) .Append(' ') .Append(Mathf.RoundToInt(storageSource.Position.DistanceTo(other))) .Append("m"); if (storageSource.ToggledOff) { stringBuilder.Append(" OFF"); } stringBuilder.Append(" count=").Append(storageSource.Count(prefab)); if (storageSource is DrawerSource drawerSource && drawerSource.TryReadRaw(out var prefab2, out var amount2, out var _)) { stringBuilder.Append(" zdo='").Append(prefab2).Append("'x") .Append(amount2); } stringBuilder.Append(']'); } } } string text = stringBuilder.ToString(); if (!readoutLogged.TryGetValue(prefab, out var value) || !(value == text)) { readoutLogged[prefab] = text; Log.LogInfo((object)text); } } [HarmonyPatch(typeof(InventoryGui), "Update")] [HarmonyPrefix] private static void InventoryGuiUpdatePrefix(InventoryGui __instance, Animator ___m_animator) { bool num = AllowByKey(); bool flag = num != lastAllowed; lastAllowed = num; if (flag && !((Object)(object)__instance == (Object)null) && !((Object)(object)___m_animator == (Object)null) && ___m_animator.GetBool("visible") && !((Object)(object)Player.m_localPlayer == (Object)null)) { MethodInfo methodInfo = AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", new Type[1] { typeof(bool) }, (Type[])null); if (!(methodInfo == null)) { methodInfo.Invoke(__instance, new object[1] { false }); } } } private static string FailureReason(Player player, Requirement[] requirements, int qualityLevel, int multiplier, FeatureFlags flags) { return MissingReason(player, requirements, qualityLevel, multiplier, flags) ?? "Not enough room in your inventory to pull these materials."; } private static string MissingReason(Player player, Requirement[] requirements, int qualityLevel, int multiplier, FeatureFlags flags) { IReadOnlyList<IStorageSource> sources; PullPlanResult pullPlanResult = PullEngine.PlanFor(requirements, qualityLevel, multiplier, player, flags, out sources); if (!pullPlanResult.Feasible) { return PullKeyRules.MissingMessage(pullPlanResult.MissingItemNames); } return null; } private static int PlacementStatus(Player player) { if ((Object)(object)player == (Object)null) { return -1; } return BuildPullRules.PlacementStatusFrom(PlacementStatusField != null, (PlacementStatusField != null) ? PlacementStatusField.GetValue(player) : null); } private static bool PreventKeyHeld(FeatureFlags flags) { if (flags != null) { return KeyHeld(flags.PreventModKey); } return false; } internal static bool AllowByKey() { FeatureFlags flags = PluginConfig.Flags; if (flags == null || !flags.ModEnabled) { return false; } return PullKeyRules.AllowByKey(flags.SwitchPrevent, KeyHeld(flags.PreventModKey)); } internal static bool KeyHeld(string key) { if (!ToggleRules.IsKeyConfigured(key)) { return false; } try { return Input.GetKey(ToggleRules.InputKeyName(key)); } catch { return false; } } private static void ShowMessage(Player player, string message) { if (!((Object)(object)player == (Object)null) && !string.IsNullOrEmpty(message)) { ((Character)player).Message((MessageType)2, message, 0, (Sprite)null, false); } } private static void Debug(FeatureFlags flags, string message) { if (flags != null && flags.IsDebug) { Log.LogInfo((object)message); } } } [HarmonyPatch] internal static class StationPatches { private enum PullSetting { Fuel, Ore, Cooking } private static readonly ManualLogSource Log = Logger.CreateLogSource("CraftFromDrawersAndChests"); private static readonly MethodInfo SmelterGetQueueSize = Resolve(typeof(Smelter), "GetQueueSize"); private static readonly MethodInfo SmelterGetFuel = Resolve(typeof(Smelter), "GetFuel"); private static readonly MethodInfo CookingStationGetFuel = Resolve(typeof(CookingStation), "GetFuel"); private static readonly MethodInfo CookingStationHaveDoneItem = Resolve(typeof(CookingStation), "HaveDoneItem"); private static readonly MethodInfo CookingStationGetFreeSlot = Resolve(typeof(CookingStation), "GetFreeSlot"); private static readonly MethodInfo CookingStationIsFireLit = Resolve(typeof(CookingStation), "IsFireLit"); private static bool _invokeFailureLogged; private static bool _returnTypeFailureLogged; private const string AddFuelRpc = "RPC_AddFuel"; private const string AddOreRpc = "RPC_AddOre"; private const string AddFuelHintToken = "$piece_smelter_add"; private static MethodInfo Resolve(Type type, string name) { MethodInfo methodInfo; try { methodInfo = AccessTools.Method(type, name, (Type[])null, (Type[])null); } catch (Exception ex) { Log.LogWarning((object)("StationPatches: resolving vanilla member " + type.Name + "." + name + "() threw; stations of this kind will not be fed from storage. " + ex.Message)); return null; } if (methodInfo == null) { Log.LogWarning((object)("StationPatches: vanilla member " + type.Name + "." + name + "() was not found; stations of this kind will not be fed from storage.")); } return methodInfo; } private static bool TryInvoke(MethodInfo method, object instance, out object result) { result = null; if (method == null || instance == null) { return false; } try { result = method.Invoke(instance, null); return true; } catch (Exception ex) { if (!_invokeFailureLogged) { _invokeFailureLogged = true; Log.LogWarning((object)("StationPatches: a vanilla station member threw on invoke; affected stations will not be fed. " + ex.Message)); } return false; } } private static void LogUnexpectedReturnType(string expected, object actual) { if (!_returnTypeFailureLogged) { _returnTypeFailureLogged = true; Log.LogWarning((object)("StationPatches: a vanilla station member returned " + ((actual == null) ? "null" : actual.GetType().Name) + " where " + expected + " was expected; affected stations will not be fed. Vanilla may have changed shape.")); } } private static bool TryInvokeInt(MethodInfo method, object instance, out int value) { value = 0; if (!TryInvoke(method, instance, out var result)) { return false; } if (!(result is int)) { LogUnexpectedReturnType("int", result); return false; } value = (int)result; return true; } private static bool TryInvokeFloat(MethodInfo method, object instance, out float value) { value = 0f; if (!TryInvoke(method, instance, out var result)) { return false; } if (!(result is float)) { LogUnexpectedReturnType("float", result); return false; } value = (float)result; return true; } private static bool TryInvokeBool(MethodInfo method, object instance, out bool value) { value = false; if (!TryInvoke(method, instance, out var result)) { return false; } if (!(result is bool)) { LogUnexpectedReturnType("bool", result); return false; } value = (bool)result; return true; } [HarmonyPatch(typeof(Fireplace), "Interact")] [HarmonyPrefix] private static bool FireplaceInteractPrefix(Fireplace __instance, Humanoid user, bool hold, ZNetView ___m_nview) { //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)___m_nview == (Object)null || hold) { return true; } FeatureFlags flags = Enabled(PullSetting.Fuel); if (flags == null) { return true; } Player player = (Player)(object)((user is Player) ? user : null); Inventory val = (((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null); if (val == null) { return true; } ItemDrop fuelItem = __instance.m_fuelItem; if (!HasItemData(fuelItem)) { return true; } bool fillAll = FillAllHeld(flags); if (!fillAll && val.HaveItem(SharedName(fuelItem), true)) { return true; } ZDO zdo = ___m_nview.GetZDO(); float fuel = ((zdo != null) ? zdo.GetFloat(ZDOVars.s_fuel, 0f) : 0f); int num = StationFeedRules.RemainingFuelCapacity(__instance.m_maxFuel, fuel); if (num <= 0) { return true; } string prefab = PrefabName(fuelItem); if (StationFeedRules.IsDisallowedType(flags.FuelDisallowTypes, prefab)) { return true; } Vector3 origin = ((Component)__instance).transform.position; return FeedStation(player, val, ___m_nview, flags, StationFeedRules.FillAmount(fillAll, num), "RPC_AddFuel", (int wanted) => (!PullFromStorage(player, origin, prefab, fuelItem, flags, wanted, fillAll)) ? null : prefab, () => FuelRemaining(__instance, () => (zdo == null) ? 0f : zdo.GetFloat(ZDOVars.s_fuel, 0f))) <= 0; } [HarmonyPatch(typeof(Smelter), "OnAddOre")] [HarmonyPrefix] private static bool SmelterOnAddOrePrefix(Smelter __instance, Humanoid user, ItemData item, ZNetView ___m_nview, ref bool __result) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || item != null) { return true; } FeatureFlags flags = Enabled(PullSetting.Ore); if (flags == null) { return true; } Player player = (Player)(object)((user is Player) ? user : null); Inventory inventory = (((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null); if (inventory == null) { return true; } if (!TryInvokeInt(SmelterGetQueueSize, __instance, out var value)) { return true; } int num = StationFeedRules.RemainingCapacity(__instance.m_maxOre, value); if (num <= 0) { return true; } bool fillAll = FillAllHeld(flags); List<ItemDrop> inputs = InputItems(__instance.m_conversion); if (InventoryHasInput(inventory, inputs)) { return true; } Vector3 origin = ((Component)__instance).transform.position; int num2 = FeedStation(player, inventory, ___m_nview, flags, StationFeedRules.FillAmount(fillAll, num), "RPC_AddOre", (int wanted) => (!PullFirstInputs(player, origin, inputs, flags.OreDisallowTypes, flags, wanted, fillAll)) ? null : FirstInputInInventory(inventory, inputs), () => QueueRemaining(__instance)); if (num2 > 0) { __result = true; } return num2 <= 0; } private static string FirstInputInInventory(Inventory inventory, List<ItemDrop> inputs) { if (inventory == null || inputs == null) { return null; } string text = null; for (int i = 0; i < inputs.Count; i++) { ItemDrop item = inputs[i]; if (HasItemData(item)) { text = SharedName(item); if (inventory.GetItem(text, -1, false) != null) { return PrefabName(item); } } } return null; } [HarmonyPatch(typeof(Smelter), "OnAddFuel")] [HarmonyPrefix] private static bool SmelterOnAddFuelPrefix(Smelter __instance, Humanoid user, ItemData item, ZNetView ___m_nview, ref bool __result) { //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || item != null) { return true; } FeatureFlags flags = Enabled(PullSetting.Fuel); if (flags == null) { return true; } Player player = (Player)(object)((user is Player) ? user : null); Inventory val = (((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null); if (val == null) { return true; } if (!TryInvokeFloat(SmelterGetFuel, __instance, out var value)) { return true; } int num = StationFeedRules.RemainingFuelCapacity(__instance.m_maxFuel, value); if (num <= 0) { return true; } ItemDrop fuelItem = __instance.m_fuelItem; if (!HasItemData(fuelItem)) { return true; } bool fillAll = FillAllHeld(flags); if (!fillAll && val.HaveItem(SharedName(fuelItem), true)) { return true; } string prefab = PrefabName(fuelItem); if (StationFeedRules.IsDisallowedType(flags.FuelDisallowTypes, prefab)) { return true; } Vector3 origin = ((Component)__instance).transform.position; int num2 = FeedStation(player, val, ___m_nview, flags, StationFeedRules.FillAmount(fillAll, num), "RPC_AddFuel", (int wanted) => (!PullFromStorage(player, origin, prefab, fuelItem, flags, wanted, fillAll)) ? null : prefab, () => SmelterFuelRemaining(__instance)); if (num2 > 0) { __result = true; } return num2 <= 0; } [HarmonyPatch(typeof(CookingStation), "OnAddFuelSwitch")] [HarmonyPrefix] private static bool CookingStationOnAddFuelSwitchPrefix(CookingStation __instance, Humanoid user, ItemData item, ZNetView ___m_nview, ref bool __result) { //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || item != null) { return true; } FeatureFlags flags = Enabled(PullSetting.Fuel); if (flags == null) { return true; } Player player = (Player)(object)((user is Player) ? user : null); Inventory val = (((Object)(object)player != (Object)null) ? ((Humanoid)player).GetInventory() : null); if (val == null) { return true; } if (!TryInvokeFloat(CookingStationGetFuel, __instance, out var value) || StationFeedRules.RemainingFuelCapacity(__instance.m_maxFuel, value) <= 0) { return true; } ItemDrop fuelItem = __instance.m_fuelItem; if (!HasItemData(fuelItem)) { return true; } if (val.HaveItem(SharedName(fuelItem), true)) { return true; } string prefab = PrefabName(fuelItem); if (StationFeedRules.IsDisallowedType(flags.FuelDisallowTypes, prefab)) { return true; } Vector3 origin = ((Component)__instance).transform.position; int num = FeedStation(player, val, ___m_nview, flags, 1, "RPC_AddFuel", (int wanted) => (!PullFromStorage(player, origin, prefab, fuelItem, flags, wanted, fillUpTo: false)) ? null : prefab, () => CookingFuelRemaining(__instance)); if (num > 0) { __result = true; } return num <= 0; } private static int FuelRemaining(Fireplace fireplace, Func<float> readFuel) { if ((Object)(object)fireplace == (Object)null || readFuel == null) { return -1; } return StationFeedRules.RemainingFuelCapacity(fireplace.m_maxFuel, readFuel()); } private static int QueueRemaining(Smelter smelter) { if ((Object)(object)smelter == (Object)null || !TryInvokeInt(SmelterGetQueueSize, smelter, out var value)) { return -1; } return StationFeedRules.RemainingCapacity(smelter.m_maxOre, value); } private static int SmelterFuelRemaining(Smelter smelter) { if ((Object)(object)smelter == (Object)null || !TryInvokeFloat(SmelterGetFuel, smelter, out var value)) { return -1; } return StationFeedRules.RemainingFuelCapacity(smelter.m_maxFuel, value); } private static int CookingFuelRemaining(CookingStation station) { if ((Object)(object)station == (Object)null || !TryInvokeFloat(CookingStationGetFuel, station, out var value)) { return -1; } return StationFeedRules.RemainingFuelCapacity(station.m_maxFuel, value); } [HarmonyPatch(typeof(CookingStation), "OnInteract")] [HarmonyPrefix] private static bool CookingStationOnInteractPrefix(CookingStation __instance, Humanoid user) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return true; } FeatureFlags featureFlags = Enabled(PullSetting.Cooking); if (featureFlags == null) { return true; } if (!TryInvokeBool(CookingStationHaveDoneItem, __instance, out var value) || value) { return true; } Player val = (Player)(object)((user is Player) ? user : null); Inventory val2 = (((Object)(object)val != (Object)null) ? ((Humanoid)val).GetInventory() : null); if (val2 == null) { return true; } if (!TryInvokeInt(CookingStationGetFreeSlot, __instance, out var value2) || value2 == -1) { return true; } if (__instance.m_requireFire && (!TryInvokeBool(CookingStationIsFireLit, __instance, out var value3) || !value3)) { return true; } List<ItemDrop> inputs = InputItems(__instance.m_conversion); if (InventoryHasInput(val2, inputs)) { return true; } PullFirstInputs(val, ((Component)__instance).transform.position, inputs, featureFlags.OreDisallowTypes, featureFlags, 1, fillUpTo: false); return true; } [HarmonyPatch(typeof(Turret), "UseItem")] [HarmonyPrefix] private static bool TurretUseItemPrefix(Turret __instance, Humanoid user, ItemData item) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || item != null) { return true; } FeatureFlags flags = PluginConfig.Flags; if (flags == null || !flags.ModEnabled) { return true; } if (!PullKeyPatches.AllowByKey()) { return true; } Player val = (Player)(object)((user is Player) ? user : null); if ((Object)(object)val == (Object)null) { return true; } if (StationFeedRules.RemainingCapacity(__instance.m_maxAmmo, __instance.GetAmmo()) <= 0) { return true; } string ammoType = __instance.GetAmmoType(); if (string.IsNullOrEmpty(ammoType)) { return true; } ItemDrop val2 = AmmoItem(ammoType); if ((Object)(object)val2 == (Object)null) { Debug(flags, "Turret: no item prefab for ammunition type '" + ammoType + "'; nothing pulled."); return true; } PullFromStorage(val, ((Component)__instance).transform.position, ammoType, val2, flags, 1, fillUpTo: false); return true; } private static int FeedStation(Player player, Inventory inventory, ZNetView nview, FeatureFlags flags, int remaining, string rpcName, Func<int, string> pullOneBatch, Func<int> remainingNow) { if ((Object)(object)player == (Object)null || inventory == null || (Object)(object)nview == (Object)null || flags == null || remaining <= 0 || pullOneBatch == null) { return 0; } int i; int num; for (i = 0; i < remaining; i += num) { string text = pullOneBatch(remaining - i); if (text == null) { break; } num = FeedStationFromInventory(inventory, nview, text, remaining - i, rpcName, remainingNow); if (num <= 0) { break; } } return i; } private static int FeedStationFromInventory(Inventory inventory, ZNetView nview, string prefab, int maxUnits, string rpcName, Func<int> remainingNow) { if (inventory == null || (Object)(object)nview == (Object)null || string.IsNullOrEmpty(prefab) || maxUnits <= 0) { return 0; } int i; for (i = 0; i < maxUnits; i++) { ItemData val = FindInventoryStack(inventory, prefab); if (val == null) { break; } bool cheated = val.m_cheated; string text = ((val.m_shared != null) ? val.m_shared.m_name : null); int num = CountInventory(inventory, prefab); inventory.RemoveItem(text, 1, -1, true); if (CountInventory(inventory, prefab) >= num) { Log.LogWarning((object)("CraftFromDrawersAndChests: the station feed could not remove '" + prefab + "' from the inventory, so it was left where it was and the feed stopped rather than repeating a removal that is not taking effect.")); break; } int num2 = remainingNow?.Invoke() ?? (-1); nview.InvokeRPC(rpcName, StationRpcArgs(rpcName, prefab, cheated)); if (num2 >= 0) { int num3 = remainingNow(); if (num3 >= 0 && num3 >= num2) { Log.LogError((object)("CraftFromDrawersAndChests: the station did not take a fed '" + prefab + "' (its remaining capacity did not fall), so the feed has stopped after losing at most one unit rather than continuing to remove items that are not arriving.")); break; } } } return i; } private static object[] StationRpcArgs(string rpcName, string prefab, bool cheated) { if (!string.Equals(rpcName, "RPC_AddOre", StringComparison.Ordinal)) { return new object[0]; } return new object[2] { prefab, cheated }; } private static int CountInventory(Inventory inventory, string prefab) { List<ItemData> allItems = inventory.GetAllItems(); if (allItems == null) { return 0; } int num = 0; for (int i = 0; i < allItems.Count; i++) { ItemData val = allItems[i]; if (val != null && !((Object)(object)val.m_dropPrefab == (Object)null) && string.Equals(((Object)val.m_dropPrefab).name, prefab, StringComparison.Ordinal)) { num += val.m_stack; } } return num; } private static ItemData FindInventoryStack(Inventory inventory, string prefab) { List<ItemData> allItems = inventory.GetAllItems(); if (allItems == null) { return null; } ItemData val = null; for (int i = 0; i < allItems.Count; i++) { ItemData val2 = allItems[i]; if (val2 != null && !((Object)(object)val2.m_dropPrefab == (Object)null) && string.Equals(((Object)val2.m_dropPrefab).name, prefab, StringComparison.Ordinal) && (val == null || val2.m_stack > val.m_stack)) { val = val2; } } return val; } private static bool PullFromStorage(Player player, Vector3 origin, string prefab, ItemDrop item, FeatureFlags flags, int amount, bool fillUpTo) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)item == (Object)null || string.IsNullOrEmpty(prefab) || flags == null || amount <= 0) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory == null) { return false; } Requirement[] requirements = (Requirement[])(object)new Requirement[1] { new Requirement { m_amount = amount, m_resItem = item } }; bool requested; bool flag = (fillUpTo ? PullEngine.PullToInventoryFill(requirements, 0, 1, inventory, Origin(origin), flags, allowUpgraderResources: false, out requested) : PullEngine.PullToInventory(requirements, 0, 1, inventory, Origin(origin), flags, allowUpgraderResources: false, out requested)); if (flag && requested) { Debug(flags, "Station: pulled up to " + amount + "x '" + prefab + "' from storage into " + ((Object)player).name + "'s inventory."); } return flag && requested; } private static bool PullFirstInputs(Player player, Vector3 origin, List<ItemDrop> inputs, string disallowTypes, FeatureFlags flags, int amount, bool fillUpTo) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (amount <= 0) { return false; } for (int i = 0; i < inputs.Count; i++) { ItemDrop val = inputs[i]; if (!((Object)(object)val == (Object)null)) { string text = PrefabName(val); if (!string.IsNullOrEmpty(text) && !StationFeedRules.IsDisallowedType(disallowTypes, text) && PullFromStorage(player, origin, text, val, flags, amount, fillUpTo)) { return true; } } } return false; } private static List<ItemDrop> InputItems(List<ItemConversion> conversions) { List<ItemDrop> list = new List<ItemDrop>(); if (conversions == null) { return list; } for (int i = 0; i < conversions.Count; i++) { list.Add(conversions[i]?.m_from); } return list; } private static List<ItemDrop> InputItems(List<ItemConversion> conversions) { List<ItemDrop> list = new List<ItemDrop>(); if (conversions == null) { return list; } for (int i = 0; i < conversions.Count; i++) { list.Add(conversions[i]?.m_from); } return list; } [HarmonyPatch(typeof(Smelter), "OnHoverAddFuel")] [HarmonyPostfix] private static void SmelterOnHoverAddFuelPostfix(ref string __result) { AppendFillAllHint(ref __result, "$piece_smelter_add", PullSetting.Fuel); } [HarmonyPatch(typeof(Fireplace), "GetHoverText")] [HarmonyPostfix] private static void FireplaceGetHoverTextPostfix(Fireplace __instance, ref string __result) { AppendFillAllHint(ref __result, "$piece_smelter_add", PullSetting.Fuel); } [HarmonyPatch(typeof(Smelter), "OnHoverAddOre")] [HarmonyPostfix] private static void SmelterOnHoverAddOrePostfix(Smelter __instance, ref string __result) { AppendFillAllHint(ref __result, ((Object)(object)__instance != (Object)null) ? __instance.m_addOreTooltip : null, PullSetting.Ore); } private static void AppendFillAllHint(ref string result, string action, PullSetting setting) { FeatureFlags flags = PluginConfig.Flags; if (flags != null && flags.ModEnabled && PullSettingOn(flags, setting)) { string text = StationFeedRules.FillAllHint(flags.FillAllModKey, action); if (text != null && Localization.instance != null) { result += Localization.instance.Localize(text); } } } private static FeatureFlags Enabled(PullSetting setting) { FeatureFlags flags = PluginConfig.Flags; if (flags == null || !flags.ModEnabled) { return null; } if (!PullKeyPatches.AllowByKey()) { return null; } if (!PullSettingOn(flags, setting)) { return null; } return flags; } private static bool PullSettingOn(FeatureFlags flags, PullSetting setting) { if (flags == null) { return false; } return setting switch { PullSetting.Fuel => flags.PullFuel, PullSetting.Ore => flags.PullOre, PullSetting.Cooking => flags.PullCooking, _ => false, }; } private static bool FillAllHeld(FeatureFlags flags) { if (flags != null) { return PullKeyPatches.KeyHeld(flags.FillAllModKey); } return false; } private static string PrefabName(ItemDrop item) { if ((Object)(object)item == (Object)null) { return null; } GameObject gameObject = ((Component)item).gameObject; if (!((Object)(object)gameObject != (Object)null)) { return null; } return ((Object)gameObject).name; } private static string SharedName(ItemDrop item) { if (!((Object)(object)item != (Object)null) || item.m_itemData == null || item.m_itemData.m_shared == null) { return null; } return item.m_itemData.m_shared.m_name; } private static bool InventoryHasInput(Inventory inventory, List<ItemDrop> inputs) { if (inventory == null || inputs == null) { return false; } for (int i = 0; i < inputs.Count; i++) { string text = SharedName(inputs[i]); if (!string.IsNullOrEmpty(text) && inventory.GetItem(text, -1, false) != null) { return true; } } return