Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of CustomStack v1.1.8
CustomStack-IL2CPP.dll
Decompiled 9 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using CustomStack; using CustomStack.Helpers; using CustomStack.Patches; using HarmonyLib; using Il2CppFishNet; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Management; using Il2CppScheduleOne.Money; using Il2CppScheduleOne.ObjectScripts; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.Storage; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Items; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(global::CustomStack.CustomStack), "CustomStack", "1.1.8", "k073l", null)] [assembly: MelonColor(1, 255, 0, 0)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("CustomStack-IL2CPP")] [assembly: AssemblyConfiguration("IL2CPP")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+34e336381dff4000536384980ccea01904d555a4")] [assembly: AssemblyProduct("CustomStack-IL2CPP")] [assembly: AssemblyTitle("CustomStack-IL2CPP")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace CustomStack { public static class BuildInfo { public const string Name = "CustomStack"; public const string Description = "Custom stack limits, per item"; public const string Author = "k073l"; public const string Version = "1.1.8"; } public class CustomStack : MelonMod { private static Instance _logger; private static MelonPreferences_Category _category; private static readonly Dictionary<string, MelonPreferences_Entry<int>> Entries = new Dictionary<string, MelonPreferences_Entry<int>>(); private static Dictionary<string, int> _defaultLimits = new Dictionary<string, int>(); private static bool _populatedDefaultLimits = false; private static MelonPreferences_Category _stations; internal static MelonPreferences_Entry<int> MaxMixQuantity; internal static MelonPreferences_Entry<int> DryingItemCapacity; public override void OnInitializeMelon() { _logger = ((MelonBase)this).LoggerInstance; _logger.Msg("CustomStack initialized."); _category = MelonPreferences.CreateCategory("CustomStack", "Stack Sizes"); _category.SetFilePath(Path.Combine(MelonEnvironment.UserDataDirectory, "CustomStack.cfg")); _stations = MelonPreferences.CreateCategory("CustomStack_Stations", "Station Capacities"); _stations.SetFilePath(Path.Combine(MelonEnvironment.UserDataDirectory, "CustomStack.cfg")); MaxMixQuantity = _stations.CreateEntry<int>("MaxMixQuantity", 20, "Max Mixing Quantity", "Controls how much you can mix in Mixing Stations", false, false, (ValueValidator)(object)new ValueRange<int>(1, 10000), (string)null); ((MelonEventBase<LemonAction<int, int>>)(object)MaxMixQuantity.OnEntryValueChanged).Subscribe((LemonAction<int, int>)MixingStationPatches.ApplyCustomLimitRuntime, 0, false); DryingItemCapacity = _stations.CreateEntry<int>("DryingItemCapacity", 20, "Drying Rack Capacity", "Controls how many items you can dry in Drying Racks", false, false, (ValueValidator)(object)new ValueRange<int>(1, 101000), (string)null); ((MelonEventBase<LemonAction<int, int>>)(object)DryingItemCapacity.OnEntryValueChanged).Subscribe((LemonAction<int, int>)DryingRackPatches.ApplyCustomLimitRuntime, 0, false); } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { if (sceneName == "Menu") { LoadAllItems(); } _logger.Debug("Scene loaded: " + sceneName); } private static void LoadAllItems() { if (_populatedDefaultLimits) { _logger.Msg("Item limits already set, skipping."); return; } _logger.Msg("Loading item stack limits..."); IOrderedEnumerable<ItemRegister> orderedEnumerable = from register in Singleton<Registry>.Instance.ItemRegistry.AsEnumerable<ItemRegister>() orderby register.name select register; _category.LoadFromFile(false); foreach (ItemRegister item in orderedEnumerable) { string iD = item.ID; int stackLimit = ((BaseItemDefinition)item.Definition).StackLimit; if (!Entries.ContainsKey(iD)) { MelonPreferences_Entry<int> value = _category.CreateEntry<int>(iD, stackLimit, ((BaseItemDefinition)item.Definition).Name, (string)null, false, false, (ValueValidator)(object)((iD == "cash") ? new ValueRange<int>(1, 100000) : new ValueRange<int>(1, 10000)), (string)null); Entries[iD] = value; } if (!_populatedDefaultLimits && !_defaultLimits.ContainsKey(iD)) { _defaultLimits[iD] = stackLimit; } MelonPreferences_Entry<int> val = Entries[iD]; int value2 = val.Value; string text = iD; string text2 = text; if (!(text2 == "cash")) { if (text2 == "defaultweed") { ((MelonPreferences_Entry)Entries[iD]).Description = "Changes to this value may be required for changing stack size of mixed weed products."; } ((BaseItemDefinition)item.Definition).StackLimit = value2; if (value2 != _defaultLimits[iD]) { _logger.Msg($"{iD} limit overridden: {_defaultLimits[iD]} → {value2}"); } } else if (value2 == _defaultLimits[iD]) { _logger.Msg("Cash stack limit not configured, forcing runtime limit to 1k (config not changed)."); ((BaseItemDefinition)item.Definition).StackLimit = 1000; } else { ((BaseItemDefinition)item.Definition).StackLimit = value2; if (value2 != _defaultLimits[iD]) { _logger.Msg($"cash limit overridden by user: {_defaultLimits[iD]} → {value2}"); } } } _populatedDefaultLimits = true; foreach (KeyValuePair<string, MelonPreferences_Entry<int>> entryKv in Entries) { ((MelonEventBase<LemonAction<int, int>>)(object)entryKv.Value.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int was, int currentLimit) { UpdateLimitRuntime(was, currentLimit, entryKv.Key, entryKv.Value); }, 0, false); } } private static void UpdateLimitRuntime(int previousLimit, int currentLimit, string itemID, MelonPreferences_Entry<int> entry) { //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) if (previousLimit == currentLimit) { return; } if (previousLimit > currentLimit) { Melon<CustomStack>.Logger.Warning($"Cannot lower stack limit ({previousLimit} -> {currentLimit}) of {itemID} at runtime! Close the game and edit the file manually if you want to proceed with a potentially destructive action."); entry.Value = previousLimit; return; } ItemRegister val = Singleton<Registry>.Instance.ItemRegistry.AsEnumerable<ItemRegister>().FirstOrDefault((Func<ItemRegister, bool>)((ItemRegister register) => register.ID == itemID)); if (val == null) { return; } ItemDefinition definition = val.Definition; if (definition == null) { return; } _ = ((BaseItemDefinition)definition).StackLimit; if (false) { return; } ((BaseItemDefinition)val.Definition).StackLimit = currentLimit; List<string> idsToSearch = new List<string>(1) { itemID }; if (Utils.Is<ProductDefinition>((object)val.Definition, out ProductDefinition result) && NetworkSingleton<ProductManager>.InstanceExists) { EDrugType val2 = (EDrugType)0; try { val2 = result.DrugType; } catch (Exception) { Melon<CustomStack>.Logger.Warning("Failed to read drug type for " + itemID + ". Assuming weed."); } Enumerator<ProductDefinition> enumerator = NetworkSingleton<ProductManager>.Instance.AllProducts.GetEnumerator(); while (enumerator.MoveNext()) { ProductDefinition current = enumerator.Current; try { if ((Object)(object)current != (Object)null && current.DrugType == val2) { idsToSearch.Add(((BaseItemDefinition)current).ID); } } catch (Exception ex2) { Melon<CustomStack>.Logger.Warning("Failed to read DrugType for product '" + (((current != null) ? ((BaseItemDefinition)current).ID : null) ?? "<null>") + "': " + ex2.Message); } } } IEnumerable<BaseItemDefinition> enumerable = from def in ((IEnumerable<BaseItemDefinition>)Object.FindObjectsOfType<BaseItemDefinition>()).AsEnumerable() where idsToSearch.Contains(def.ID) select def; foreach (BaseItemDefinition item in enumerable) { item.StackLimit = currentLimit; } IEnumerable<string> values = enumerable.Select((BaseItemDefinition def) => def.ID); Melon<CustomStack>.Logger.Msg($"Updated limit of {string.Join(',', values)} to {currentLimit}."); } } } namespace CustomStack.Patches { [HarmonyPatch(typeof(ItemUIManager))] public static class CashPatches { private static int CashItemLimit; private static Instance Logger; [HarmonyPatch("StartDragCash")] [HarmonyPrefix] private static bool StartDragCash(ItemUIManager __instance) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) Logger.Debug("StartDragCash: top"); ItemInstance itemInstance = __instance.draggedSlot.assignedSlot.ItemInstance; if (!Utils.Is<CashInstance>((object)itemInstance, out CashInstance result)) { Logger.Debug("StartDragCash: cashInstance is null"); } Logger.Debug("StartDragCash: got cashInstance"); __instance.draggedCashAmount = Mathf.Min(result.Balance, (float)CashItemLimit); __instance.draggedAmount = 1; __instance.customDragAmount = GameInput.GetCurrentInputDeviceIsGamepad(); Logger.Debug("Set draggedCashAmount and draggedAmount"); if (GameInput.GetButtonDown((ButtonCode)1) || __instance.controllerQuickMoveSingle) { Logger.Debug("StartDragCash: secondary click"); __instance.draggedAmount = 1; __instance.draggedCashAmount = Mathf.Min(result.Balance, Mathf.Floor((float)CashItemLimit / 10f)); __instance.mouseOffset += new Vector2(-10f, -15f); __instance.customDragAmount = true; } Logger.Debug("StartDragCash: draggedAmount check"); if (__instance.draggedCashAmount <= 0f) { Logger.Debug("StartDragCash: no cash to drag"); __instance.draggedSlot = null; } else if ((Input.GetKey((KeyCode)304) || __instance.canControllerQuickMove) && __instance.QuickMoveEnabled) { Logger.Debug("StartDragCash: quickmove"); List<ItemSlot> list = Il2CppListExtensions.ConvertToList<ItemSlot>(__instance.GetQuickMoveSlots(__instance.draggedSlot.assignedSlot)); if (list.Count > 0) { Logger.Debug("StartDragCash: quickmove slots found"); Debug.Log(Object.op_Implicit("Quick-moving " + __instance.draggedAmount + " items...")); float draggedCashAmount = __instance.draggedCashAmount; float num = 0f; for (int i = 0; i < list.Count; i++) { if (num >= (float)__instance.draggedAmount) { break; } ItemSlot val = list[i]; if (val.ItemInstance != null) { if (Utils.Is<CashInstance>((object)val.ItemInstance, out CashInstance result2)) { float num2 = 0f; num2 = ((!Utils.Is<CashSlot>((object)val, out CashSlot _)) ? Mathf.Min(draggedCashAmount, (float)CashItemLimit - result2.Balance) : Mathf.Min(draggedCashAmount, float.MaxValue - result2.Balance)); result2.ChangeBalance(num2); val.ReplicateStoredInstance(); num += num2; } } else { if (!Utils.Is<CashInstance>((object)Registry.GetItem("cash").GetDefaultInstance(1), out CashInstance result4)) { Logger.Error("StartDragCash: failed to get default CashInstance"); return false; } result4.SetBalance(__instance.draggedCashAmount, false); val.SetStoredItem((ItemInstance)(object)result4, false); num += __instance.draggedCashAmount; } } if (num >= result.Balance) { __instance.draggedSlot.assignedSlot.ClearStoredInstance(false); } else { result.ChangeBalance(0f - num); __instance.draggedSlot.assignedSlot.ReplicateStoredInstance(); } } if (__instance.onItemMoved != null) { Logger.Debug("StartDragCash: invoking onItemMoved"); __instance.onItemMoved.Invoke(); } __instance.draggedSlot = null; } else { Logger.Debug("StartDragCash: standard drag"); if (__instance.onDragStart != null) { Logger.Debug("StartDragCash: invoking onDragStart"); __instance.onDragStart.Invoke(); } if ((object)__instance.draggedSlot.assignedSlot != PlayerSingleton<PlayerInventory>.Instance.cashSlot) { Logger.Debug("StartDragCash: play cash slot hint anim"); Singleton<HUD>.Instance.CashSlotHintAnim.Play(); } __instance.tempIcon = __instance.draggedSlot.DuplicateIcon(((Component)Singleton<HUD>.Instance).transform, __instance.draggedAmount); ((TMP_Text)((Component)((Transform)__instance.tempIcon).Find("Balance")).GetComponent<TextMeshProUGUI>()).text = MoneyManager.FormatAmount(__instance.draggedCashAmount, false, false); __instance.draggedSlot.IsBeingDragged = true; if (__instance.draggedCashAmount >= result.Balance) { Logger.Debug("StartDragCash: hide dragged slot"); __instance.draggedSlot.SetVisible(false); } else { Logger.Debug("StartDragCash: update cash balance display"); if (Utils.Is<ItemUI_Cash>((object)__instance.draggedSlot.ItemUI, out ItemUI_Cash result5)) { result5.SetDisplayedBalance(result.Balance - __instance.draggedCashAmount); } else { Logger.Debug("StartDragCash: draggedSlot is not ItemUI_Cash, cannot update balance display"); } } } Logger.Debug("StartDragCash: end"); return false; } [HarmonyPatch("EndCashDrag")] [HarmonyPrefix] private static bool EndCashDrag(ItemUIManager __instance) { CashInstance val = null; if ((Object)(object)__instance.draggedSlot != (Object)null && __instance.draggedSlot.assignedSlot != null) { if (!Utils.Is<CashInstance>((object)__instance.draggedSlot.assignedSlot.ItemInstance, out CashInstance result)) { Logger.Error("EndCashDrag: draggedSlot.assignedSlot.ItemInstance is not CashInstance"); return true; } val = result; } if (val == null) { Logger.Error("EndCashDrag: cashInstance is null"); return true; } Singleton<HUD>.Instance.CashSlotHintAnim.Stop(); Singleton<HUD>.Instance.CashSlotHintAnimCanvasGroup.alpha = 0f; bool flag = false; ItemSlotUI hoveredSlot = __instance.HoveredSlot; ItemSlot val2 = ((hoveredSlot != null) ? hoveredSlot.assignedSlot : null); ItemSlotUI draggedSlot = __instance.draggedSlot; object obj; if (draggedSlot == null) { obj = null; } else { ItemSlot assignedSlot = draggedSlot.assignedSlot; obj = ((assignedSlot != null) ? assignedSlot.ItemInstance : null); } ItemInstance val3 = (ItemInstance)obj; if (__instance.CanDragFromSlot(__instance.draggedSlot) && (Object)(object)__instance.HoveredSlot != (Object)null && __instance.CanCashBeDraggedIntoSlot(__instance.HoveredSlot) && val2 != null && val3 != null && !val2.IsLocked && !val2.IsAddLocked) { MethodInfo method = ((object)val2).GetType().GetMethod("DoesItemMatchHardFilters"); MethodInfo method2 = ((object)val2).GetType().GetMethod("DoesItemMatchPlayerFilters"); if (method != null && method2 != null) { bool flag2 = (bool)method.Invoke(val2, new object[1] { val3 }); bool flag3 = (bool)method2.Invoke(val2, new object[1] { val3 }); flag = flag2 && flag3; } else { MethodInfo method3 = ((object)val2).GetType().GetMethod("DoesItemMatchFilters"); if (method3 != null) { flag = (bool)method3.Invoke(val2, new object[1] { val3 }); } } } if (flag) { if (Utils.Is<HotbarSlot>((object)__instance.HoveredSlot.assignedSlot, out HotbarSlot _) && !Utils.Is<CashSlot>((object)__instance.HoveredSlot.assignedSlot, out CashSlot result3)) { __instance.HoveredSlot = (ItemSlotUI)(object)((Component)Singleton<HUD>.Instance.cashSlotUI).GetComponent<CashSlotUI>(); } float num = Mathf.Min(__instance.draggedCashAmount, val.Balance); if (num > 0f) { float num2 = num; if (__instance.HoveredSlot.assignedSlot.ItemInstance != null) { if (!Utils.Is<CashInstance>((object)__instance.HoveredSlot.assignedSlot.ItemInstance, out CashInstance result4)) { Logger.Error("EndCashDrag: HoveredSlot.assignedSlot.ItemInstance is not CashInstance"); } num2 = ((!Utils.Is<CashSlot>((object)__instance.HoveredSlot.assignedSlot, out result3)) ? Mathf.Min(num, (float)CashItemLimit - result4.Balance) : Mathf.Min(num, float.MaxValue - result4.Balance)); result4.ChangeBalance(num2); __instance.HoveredSlot.assignedSlot.ReplicateStoredInstance(); } else { if (!Utils.Is<CashInstance>((object)Registry.GetItem("cash").GetDefaultInstance(1), out CashInstance result5)) { Logger.Error("EndCashDrag: failed to get default CashInstance"); } result5.SetBalance(num2, false); __instance.HoveredSlot.assignedSlot.SetStoredItem((ItemInstance)(object)result5, false); } if (num2 >= val.Balance) { __instance.draggedSlot.assignedSlot.ClearStoredInstance(false); } else { val.ChangeBalance(0f - num2); __instance.draggedSlot.assignedSlot.ReplicateStoredInstance(); } } } __instance.draggedSlot.SetVisible(true); __instance.draggedSlot.UpdateUI(); __instance.draggedSlot.IsBeingDragged = false; __instance.draggedSlot = null; Object.Destroy((Object)(object)((Component)__instance.tempIcon).gameObject); Singleton<CursorManager>.Instance.SetCursorAppearance((ECursorType)0); return false; } [HarmonyPatch("UpdateCashDragAmount")] [HarmonyPrefix] private static bool UpdateCashDragAmount(ItemUIManager __instance, CashInstance instance) { float[] array = new float[3] { 50f, 10f, 1f }; float[] array2 = new float[3] { 100f, 10f, 1f }; float num = 0f; if (GameInput.MouseScrollDelta > 0f) { for (int i = 0; i < array.Length; i++) { if (__instance.draggedCashAmount >= array2[i]) { num = array[i]; break; } } } else if (GameInput.MouseScrollDelta < 0f) { for (int j = 0; j < array.Length; j++) { if (__instance.draggedCashAmount > array2[j]) { num = 0f - array[j]; break; } } } if (num != 0f) { __instance.draggedCashAmount = Mathf.Clamp(__instance.draggedCashAmount + num, 1f, Mathf.Min(instance.Balance, (float)CashItemLimit)); } return false; } [HarmonyPatch(typeof(ItemUIManager), "Update")] [HarmonyPrefix] private static bool Update(ItemUIManager __instance) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) if (GameInput.GetCurrentInputDeviceIsGamepad()) { __instance.UpdateControllerTooltip(); return false; } __instance.HoveredSlot = null; if (__instance.DraggingEnabled) { ECursorType cursorAppearance = (ECursorType)0; __instance.HoveredSlot = __instance.GetHoveredItemSlot(); if ((Object)(object)__instance.HoveredSlot != (Object)null && __instance.CanDragFromSlot(__instance.HoveredSlot)) { cursorAppearance = (ECursorType)2; } if ((Object)(object)__instance.HoveredSlot != (Object)null && (Object)(object)__instance.draggedSlot == (Object)null && __instance.HoveredSlot.assignedSlot != null && __instance.HoveredSlot.assignedSlot.Quantity > 0) { if (__instance.InfoPanel.CurrentItem != __instance.HoveredSlot.assignedSlot.ItemInstance && __instance.HoveredSlot.ShowItemInfoPanelWhenHovered()) { __instance.InfoPanel.Open(__instance.HoveredSlot.assignedSlot.ItemInstance, __instance.HoveredSlot.Rect); } } else { ItemDefinitionInfoHoverable hoveredItemInfo = __instance.GetHoveredItemInfo(); if ((Object)(object)hoveredItemInfo != (Object)null && Utils.Is<RectTransform>((object)((Component)hoveredItemInfo).transform, out RectTransform result)) { __instance.InfoPanel.Open(hoveredItemInfo.AssignedItem, result); } else if (__instance.InfoPanel.IsOpen) { __instance.InfoPanel.Close(); } } if ((Object)(object)__instance.draggedSlot != (Object)null) { cursorAppearance = (ECursorType)3; if (!GameInput.GetButton((ButtonCode)0) && !GameInput.GetButton((ButtonCode)1) && !GameInput.GetButton((ButtonCode)2)) { __instance.EndDrag(); } } else if ((GameInput.GetButtonDown((ButtonCode)0) || GameInput.GetButtonDown((ButtonCode)1) || GameInput.GetButtonDown((ButtonCode)2)) && (Object)(object)__instance.HoveredSlot != (Object)null) { __instance.SlotClicked(__instance.HoveredSlot); } Singleton<CursorManager>.Instance.SetCursorAppearance(cursorAppearance); } if ((Object)(object)__instance.draggedSlot != (Object)null && __instance.customDragAmount) { if (__instance.isDraggingCash && Utils.Is<CashInstance>((object)__instance.draggedSlot.assignedSlot.ItemInstance, out CashInstance result2)) { __instance.UpdateCashDragAmount(result2); } else if (GameInput.MouseScrollDelta > 0f) { __instance.SetDraggedAmount(Mathf.Clamp(__instance.draggedAmount + 1, 1, __instance.draggedSlot.assignedSlot.Quantity)); } else if (GameInput.MouseScrollDelta < 0f) { __instance.SetDraggedAmount(Mathf.Clamp(__instance.draggedAmount - 1, 1, __instance.draggedSlot.assignedSlot.Quantity)); } } return false; } static CashPatches() { ItemRegister? obj = Singleton<Registry>.Instance.ItemRegistry.AsEnumerable<ItemRegister>().FirstOrDefault((Func<ItemRegister, bool>)((ItemRegister x) => x.ID == "cash")); int? obj2; if (obj == null) { obj2 = null; } else { ItemDefinition definition = obj.Definition; obj2 = ((definition != null) ? new int?(((BaseItemDefinition)definition).StackLimit) : ((int?)null)); } CashItemLimit = obj2 ?? 1000; Logger = Melon<CustomStack>.Logger; } } [HarmonyPatch(typeof(DryingRack))] internal static class DryingRackPatches { [HarmonyPatch("Awake")] [HarmonyPostfix] private static void ApplyCustomLimitAwake(DryingRack __instance) { if (__instance == null) { return; } _ = __instance.ItemCapacity; if (false) { return; } __instance.ItemCapacity = CustomStack.DryingItemCapacity.Value; if (__instance == null) { return; } DryingRackConfiguration stationConfiguration = __instance.stationConfiguration; if (stationConfiguration == null) { return; } NumberField startThreshold = stationConfiguration.StartThreshold; if (startThreshold != null) { _ = startThreshold.MaxValue; if (0 == 0) { __instance.stationConfiguration.StartThreshold.MaxValue = CustomStack.DryingItemCapacity.Value; } } } internal static void ApplyCustomLimitRuntime(int _, int current) { Il2CppArrayBase<DryingRack> val = Object.FindObjectsOfType<DryingRack>(); foreach (DryingRack item in val) { item.ItemCapacity = current; item.stationConfiguration.StartThreshold.MaxValue = current; } } } [HarmonyPatch(typeof(MixingStation))] internal static class MixingStationPatches { [HarmonyPatch("NetworkInitialize___Early")] [HarmonyPostfix] private static void ApplyCustomLimitAwake(MixingStation __instance) { if (__instance == null) { return; } _ = __instance.MaxMixQuantity; if (false) { return; } __instance.MaxMixQuantity = CustomStack.MaxMixQuantity.Value; if (__instance == null) { return; } MixingStationConfiguration stationConfiguration = __instance.stationConfiguration; if (stationConfiguration == null) { return; } NumberField startThrehold = stationConfiguration.StartThrehold; if (startThrehold != null) { _ = startThrehold.MaxValue; if (0 == 0) { __instance.stationConfiguration.StartThrehold.MaxValue = CustomStack.MaxMixQuantity.Value; } } } internal static void ApplyCustomLimitRuntime(int _, int current) { Il2CppArrayBase<MixingStation> val = Object.FindObjectsOfType<MixingStation>(); foreach (MixingStation item in val) { item.MaxMixQuantity = current; item.stationConfiguration.StartThrehold.MaxValue = current; } } } internal static class StorageVisualizerState { public class State { public bool RefreshQueued; } public static ConditionalWeakTable<StorageVisualizer, State> Table = new ConditionalWeakTable<StorageVisualizer, State>(); } [HarmonyPatch(typeof(StorageVisualizer), "QueueRefresh")] internal class QueueRefreshPatch { private static bool Prefix(StorageVisualizer __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (__instance.BlockRefreshes) { return false; } StorageVisualizerState.State orCreateValue = StorageVisualizerState.Table.GetOrCreateValue(__instance); if (orCreateValue == null) { return true; } if (orCreateValue.RefreshQueued) { Melon<CustomStack>.Logger.Debug("Queued refresh for " + ((__instance != null) ? ((Object)__instance).name : null) + ". Skipping."); return false; } orCreateValue.RefreshQueued = true; Melon<CustomStack>.Logger.Debug("Starting delayed refresh for " + ((__instance != null) ? ((Object)__instance).name : null)); MelonCoroutines.Start(DelayedRefresh(__instance)); return false; } private static IEnumerator DelayedRefresh(StorageVisualizer instance) { yield return null; StorageVisualizerState.State state = StorageVisualizerState.Table.GetOrCreateValue(instance); if (state != null) { state.RefreshQueued = false; if ((Object)(object)instance != (Object)null) { instance.RefreshVisuals(); } } } } } namespace CustomStack.Helpers { public static class MelonLoggerExtensions { public static void Debug(this Instance logger, string message, bool stacktrace = true) { MelonDebug.Msg(stacktrace ? ("[" + GetCallerInfo() + "] " + message) : message); } private static string GetCallerInfo() { StackTrace stackTrace = new StackTrace(); for (int i = 2; i < stackTrace.FrameCount; i++) { StackFrame frame = stackTrace.GetFrame(i); MethodBase method = frame.GetMethod(); if (!(method?.DeclaringType == null)) { return method.DeclaringType.FullName + "." + method.Name; } } return "unknown"; } } public static class Il2CppListExtensions { public static IEnumerable<T> AsEnumerable<T>(this List<T> list) { return list ?? new List<T>(); } public static List<T> ToIl2CppList<T>(this IEnumerable<T> source) { List<T> val = new List<T>(); foreach (T item in source) { val.Add(item); } return val; } public static List<T> ConvertToList<T>(List<T> il2CppList) { List<T> list = new List<T>(); T[] collection = Il2CppArrayBase<T>.op_Implicit(il2CppList.ToArray()); list.AddRange(collection); return list; } public static IEnumerable<T> AsEnumerable<T>(this List<T> list) { IEnumerable<T> result; if (list != null) { result = ((IEnumerable<T>)list._items).Take(list._size); } else { IEnumerable<T> enumerable = Array.Empty<T>(); result = enumerable; } return result; } } public static class Utils { private static readonly Instance Logger = new Instance("CustomStack-Utils"); public static T FindObjectByName<T>(string objectName) where T : Object { try { foreach (T item in Resources.FindObjectsOfTypeAll<T>()) { if (((Object)item).name != objectName) { continue; } Logger.Debug($"Found {typeof(T).Name} '{objectName}' directly in loaded objects"); return item; } return default(T); } catch (Exception ex) { Logger.Error($"Error finding {typeof(T).Name} '{objectName}': {ex.Message}"); return default(T); } } public static List<T> GetAllComponentsInChildrenRecursive<T>(GameObject obj) where T : Component { List<T> list = new List<T>(); if ((Object)(object)obj == (Object)null) { return list; } T[] array = Il2CppArrayBase<T>.op_Implicit(obj.GetComponents<T>()); if (array.Length != 0) { list.AddRange(array); } for (int i = 0; i < obj.transform.childCount; i++) { Transform child = obj.transform.GetChild(i); list.AddRange(GetAllComponentsInChildrenRecursive<T>(((Component)child).gameObject)); } return list; } public static bool Is<T>(object obj, out T result) where T : Object { Object val = (Object)((obj is Object) ? obj : null); if (val != null) { Type val2 = Il2CppType.Of<T>(); Type il2CppType = val.GetIl2CppType(); if (val2.IsAssignableFrom(il2CppType)) { result = ((Il2CppObjectBase)val).TryCast<T>(); return result != null; } } result = default(T); return false; } public static IEnumerator WaitForPlayer(IEnumerator routine) { while ((Object)(object)Player.Local == (Object)null || (Object)(object)((Component)Player.Local).gameObject == (Object)null) { yield return null; } MelonCoroutines.Start(routine); } public static IEnumerator WaitForNetwork(IEnumerator routine) { while (!InstanceFinder.IsServer && !InstanceFinder.IsClient) { yield return null; } MelonCoroutines.Start(routine); } public static IEnumerator WaitForNotNull(object? obj, float timeout = float.NaN, Action onTimeout = null, Action onFinish = null) { float startTime = Time.time; while (obj == null) { if (!float.IsNaN(timeout) && Time.time - startTime > timeout) { onTimeout?.Invoke(); yield break; } yield return null; } onFinish?.Invoke(); } public static IEnumerator WaitForNetworkSingleton<T>(IEnumerator coroutine) where T : NetworkSingleton<T> { while (!NetworkSingleton<T>.InstanceExists) { yield return null; } yield return coroutine; } } }
CustomStack-Mono.dll
Decompiled 9 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using CustomStack; using CustomStack.Helpers; using CustomStack.Patches; using FishNet; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using ScheduleOne; using ScheduleOne.Core.Items.Framework; using ScheduleOne.DevUtilities; using ScheduleOne.ItemFramework; using ScheduleOne.Management; using ScheduleOne.Money; using ScheduleOne.ObjectScripts; using ScheduleOne.PlayerScripts; using ScheduleOne.Product; using ScheduleOne.Storage; using ScheduleOne.UI; using ScheduleOne.UI.Items; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(global::CustomStack.CustomStack), "CustomStack", "1.1.8", "k073l", null)] [assembly: MelonColor(1, 255, 0, 0)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("CustomStack-Mono")] [assembly: AssemblyConfiguration("Mono")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+34e336381dff4000536384980ccea01904d555a4")] [assembly: AssemblyProduct("CustomStack-Mono")] [assembly: AssemblyTitle("CustomStack-Mono")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace CustomStack { public static class BuildInfo { public const string Name = "CustomStack"; public const string Description = "Custom stack limits, per item"; public const string Author = "k073l"; public const string Version = "1.1.8"; } public class CustomStack : MelonMod { private static Instance _logger; private static MelonPreferences_Category _category; private static readonly Dictionary<string, MelonPreferences_Entry<int>> Entries = new Dictionary<string, MelonPreferences_Entry<int>>(); private static Dictionary<string, int> _defaultLimits = new Dictionary<string, int>(); private static bool _populatedDefaultLimits = false; private static MelonPreferences_Category _stations; internal static MelonPreferences_Entry<int> MaxMixQuantity; internal static MelonPreferences_Entry<int> DryingItemCapacity; public override void OnInitializeMelon() { _logger = ((MelonBase)this).LoggerInstance; _logger.Msg("CustomStack initialized."); _category = MelonPreferences.CreateCategory("CustomStack", "Stack Sizes"); _category.SetFilePath(Path.Combine(MelonEnvironment.UserDataDirectory, "CustomStack.cfg")); _stations = MelonPreferences.CreateCategory("CustomStack_Stations", "Station Capacities"); _stations.SetFilePath(Path.Combine(MelonEnvironment.UserDataDirectory, "CustomStack.cfg")); MaxMixQuantity = _stations.CreateEntry<int>("MaxMixQuantity", 20, "Max Mixing Quantity", "Controls how much you can mix in Mixing Stations", false, false, (ValueValidator)(object)new ValueRange<int>(1, 10000), (string)null); ((MelonEventBase<LemonAction<int, int>>)(object)MaxMixQuantity.OnEntryValueChanged).Subscribe((LemonAction<int, int>)MixingStationPatches.ApplyCustomLimitRuntime, 0, false); DryingItemCapacity = _stations.CreateEntry<int>("DryingItemCapacity", 20, "Drying Rack Capacity", "Controls how many items you can dry in Drying Racks", false, false, (ValueValidator)(object)new ValueRange<int>(1, 101000), (string)null); ((MelonEventBase<LemonAction<int, int>>)(object)DryingItemCapacity.OnEntryValueChanged).Subscribe((LemonAction<int, int>)DryingRackPatches.ApplyCustomLimitRuntime, 0, false); } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { if (sceneName == "Menu") { LoadAllItems(); } _logger.Debug("Scene loaded: " + sceneName); } private static void LoadAllItems() { if (_populatedDefaultLimits) { _logger.Msg("Item limits already set, skipping."); return; } _logger.Msg("Loading item stack limits..."); IOrderedEnumerable<ItemRegister> orderedEnumerable = from register in Singleton<Registry>.Instance.ItemRegistry.AsEnumerable() orderby register.name select register; _category.LoadFromFile(false); foreach (ItemRegister item in orderedEnumerable) { string iD = item.ID; int stackLimit = ((BaseItemDefinition)item.Definition).StackLimit; if (!Entries.ContainsKey(iD)) { MelonPreferences_Entry<int> value = _category.CreateEntry<int>(iD, stackLimit, ((BaseItemDefinition)item.Definition).Name, (string)null, false, false, (ValueValidator)(object)((iD == "cash") ? new ValueRange<int>(1, 100000) : new ValueRange<int>(1, 10000)), (string)null); Entries[iD] = value; } if (!_populatedDefaultLimits && !_defaultLimits.ContainsKey(iD)) { _defaultLimits[iD] = stackLimit; } MelonPreferences_Entry<int> val = Entries[iD]; int value2 = val.Value; string text = iD; string text2 = text; if (!(text2 == "cash")) { if (text2 == "defaultweed") { ((MelonPreferences_Entry)Entries[iD]).Description = "Changes to this value may be required for changing stack size of mixed weed products."; } ((BaseItemDefinition)item.Definition).StackLimit = value2; if (value2 != _defaultLimits[iD]) { _logger.Msg($"{iD} limit overridden: {_defaultLimits[iD]} → {value2}"); } } else if (value2 == _defaultLimits[iD]) { _logger.Msg("Cash stack limit not configured, forcing runtime limit to 1k (config not changed)."); ((BaseItemDefinition)item.Definition).StackLimit = 1000; } else { ((BaseItemDefinition)item.Definition).StackLimit = value2; if (value2 != _defaultLimits[iD]) { _logger.Msg($"cash limit overridden by user: {_defaultLimits[iD]} → {value2}"); } } } _populatedDefaultLimits = true; foreach (KeyValuePair<string, MelonPreferences_Entry<int>> entryKv in Entries) { ((MelonEventBase<LemonAction<int, int>>)(object)entryKv.Value.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int was, int currentLimit) { UpdateLimitRuntime(was, currentLimit, entryKv.Key, entryKv.Value); }, 0, false); } } private static void UpdateLimitRuntime(int previousLimit, int currentLimit, string itemID, MelonPreferences_Entry<int> entry) { //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) if (previousLimit == currentLimit) { return; } if (previousLimit > currentLimit) { Melon<CustomStack>.Logger.Warning($"Cannot lower stack limit ({previousLimit} -> {currentLimit}) of {itemID} at runtime! " + "Close the game and edit the file manually if you want to proceed with a potentially destructive action."); entry.Value = previousLimit; return; } ItemRegister val = Singleton<Registry>.Instance.ItemRegistry.AsEnumerable().FirstOrDefault((Func<ItemRegister, bool>)((ItemRegister register) => register.ID == itemID)); if (val == null) { return; } ItemDefinition definition = val.Definition; if (definition == null) { return; } _ = ((BaseItemDefinition)definition).StackLimit; if (false) { return; } ((BaseItemDefinition)val.Definition).StackLimit = currentLimit; List<string> idsToSearch = new List<string>(1) { itemID }; if (Utils.Is<ProductDefinition>(val.Definition, out var result) && NetworkSingleton<ProductManager>.InstanceExists) { EDrugType val2 = (EDrugType)0; try { val2 = result.DrugType; } catch (Exception) { Melon<CustomStack>.Logger.Warning("Failed to read drug type for " + itemID + ". Assuming weed."); } foreach (ProductDefinition allProduct in NetworkSingleton<ProductManager>.Instance.AllProducts) { try { if ((Object)(object)allProduct != (Object)null && allProduct.DrugType == val2) { idsToSearch.Add(((BaseItemDefinition)allProduct).ID); } } catch (Exception ex2) { Melon<CustomStack>.Logger.Warning("Failed to read DrugType for product '" + (((BaseItemDefinition)(allProduct?)).ID ?? "<null>") + "': " + ex2.Message); } } } IEnumerable<BaseItemDefinition> enumerable = from def in Object.FindObjectsOfType<BaseItemDefinition>().AsEnumerable() where idsToSearch.Contains(def.ID) select def; foreach (BaseItemDefinition item in enumerable) { item.StackLimit = currentLimit; } IEnumerable<string> values = enumerable.Select((BaseItemDefinition def) => def.ID); Melon<CustomStack>.Logger.Msg($"Updated limit of {string.Join(',', values)} to {currentLimit}."); } } } namespace CustomStack.Patches { [HarmonyPatch(typeof(ItemUIManager))] public static class CashPatches { private static int CashItemLimit = ((BaseItemDefinition)(Singleton<Registry>.Instance.ItemRegistry.AsEnumerable().FirstOrDefault((Func<ItemRegister, bool>)((ItemRegister x) => x.ID == "cash"))?.Definition?)).StackLimit ?? 1000; private static Instance Logger = Melon<CustomStack>.Logger; [HarmonyPatch("StartDragCash")] [HarmonyPrefix] private static bool StartDragCash(ItemUIManager __instance) { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) Logger.Debug("StartDragCash: top"); ItemInstance itemInstance = __instance.draggedSlot.assignedSlot.ItemInstance; if (!Utils.Is<CashInstance>(itemInstance, out var result)) { Logger.Debug("StartDragCash: cashInstance is null"); } Logger.Debug("StartDragCash: got cashInstance"); __instance.draggedCashAmount = Mathf.Min(result.Balance, (float)CashItemLimit); __instance.draggedAmount = 1; __instance.customDragAmount = GameInput.GetCurrentInputDeviceIsGamepad(); Logger.Debug("Set draggedCashAmount and draggedAmount"); if (GameInput.GetButtonDown((ButtonCode)1) || __instance.controllerQuickMoveSingle) { Logger.Debug("StartDragCash: secondary click"); __instance.draggedAmount = 1; __instance.draggedCashAmount = Mathf.Min(result.Balance, Mathf.Floor((float)CashItemLimit / 10f)); __instance.mouseOffset += new Vector2(-10f, -15f); __instance.customDragAmount = true; } Logger.Debug("StartDragCash: draggedAmount check"); if (__instance.draggedCashAmount <= 0f) { Logger.Debug("StartDragCash: no cash to drag"); __instance.draggedSlot = null; } else if ((Input.GetKey((KeyCode)304) || __instance.canControllerQuickMove) && __instance.QuickMoveEnabled) { Logger.Debug("StartDragCash: quickmove"); List<ItemSlot> quickMoveSlots = __instance.GetQuickMoveSlots(__instance.draggedSlot.assignedSlot); if (quickMoveSlots.Count > 0) { Logger.Debug("StartDragCash: quickmove slots found"); Debug.Log((object)("Quick-moving " + __instance.draggedAmount + " items...")); float draggedCashAmount = __instance.draggedCashAmount; float num = 0f; for (int i = 0; i < quickMoveSlots.Count; i++) { if (num >= (float)__instance.draggedAmount) { break; } ItemSlot val = quickMoveSlots[i]; if (val.ItemInstance != null) { if (Utils.Is<CashInstance>(val.ItemInstance, out var result2)) { float num2 = 0f; num2 = ((!Utils.Is<CashSlot>(val, out var _)) ? Mathf.Min(draggedCashAmount, (float)CashItemLimit - result2.Balance) : Mathf.Min(draggedCashAmount, float.MaxValue - result2.Balance)); result2.ChangeBalance(num2); val.ReplicateStoredInstance(); num += num2; } } else { if (!Utils.Is<CashInstance>(Registry.GetItem("cash").GetDefaultInstance(1), out var result4)) { Logger.Error("StartDragCash: failed to get default CashInstance"); return false; } result4.SetBalance(__instance.draggedCashAmount, false); val.SetStoredItem((ItemInstance)(object)result4, false); num += __instance.draggedCashAmount; } } if (num >= result.Balance) { __instance.draggedSlot.assignedSlot.ClearStoredInstance(false); } else { result.ChangeBalance(0f - num); __instance.draggedSlot.assignedSlot.ReplicateStoredInstance(); } } if (__instance.onItemMoved != null) { Logger.Debug("StartDragCash: invoking onItemMoved"); __instance.onItemMoved.Invoke(); } __instance.draggedSlot = null; } else { Logger.Debug("StartDragCash: standard drag"); if (__instance.onDragStart != null) { Logger.Debug("StartDragCash: invoking onDragStart"); __instance.onDragStart.Invoke(); } if ((object)__instance.draggedSlot.assignedSlot != PlayerSingleton<PlayerInventory>.Instance.cashSlot) { Logger.Debug("StartDragCash: play cash slot hint anim"); Singleton<HUD>.Instance.CashSlotHintAnim.Play(); } __instance.tempIcon = __instance.draggedSlot.DuplicateIcon(((Component)Singleton<HUD>.Instance).transform, __instance.draggedAmount); ((TMP_Text)((Component)((Transform)__instance.tempIcon).Find("Balance")).GetComponent<TextMeshProUGUI>()).text = MoneyManager.FormatAmount(__instance.draggedCashAmount, false, false); __instance.draggedSlot.IsBeingDragged = true; if (__instance.draggedCashAmount >= result.Balance) { Logger.Debug("StartDragCash: hide dragged slot"); __instance.draggedSlot.SetVisible(false); } else { Logger.Debug("StartDragCash: update cash balance display"); if (Utils.Is<ItemUI_Cash>(__instance.draggedSlot.ItemUI, out var result5)) { result5.SetDisplayedBalance(result.Balance - __instance.draggedCashAmount); } else { Logger.Debug("StartDragCash: draggedSlot is not ItemUI_Cash, cannot update balance display"); } } } Logger.Debug("StartDragCash: end"); return false; } [HarmonyPatch("EndCashDrag")] [HarmonyPrefix] private static bool EndCashDrag(ItemUIManager __instance) { CashInstance val = null; if ((Object)(object)__instance.draggedSlot != (Object)null && __instance.draggedSlot.assignedSlot != null) { if (!Utils.Is<CashInstance>(__instance.draggedSlot.assignedSlot.ItemInstance, out var result)) { Logger.Error("EndCashDrag: draggedSlot.assignedSlot.ItemInstance is not CashInstance"); return true; } val = result; } if (val == null) { Logger.Error("EndCashDrag: cashInstance is null"); return true; } Singleton<HUD>.Instance.CashSlotHintAnim.Stop(); Singleton<HUD>.Instance.CashSlotHintAnimCanvasGroup.alpha = 0f; bool flag = false; ItemSlotUI hoveredSlot = __instance.HoveredSlot; ItemSlot val2 = ((hoveredSlot != null) ? hoveredSlot.assignedSlot : null); ItemSlotUI draggedSlot = __instance.draggedSlot; object obj; if (draggedSlot == null) { obj = null; } else { ItemSlot assignedSlot = draggedSlot.assignedSlot; obj = ((assignedSlot != null) ? assignedSlot.ItemInstance : null); } ItemInstance val3 = (ItemInstance)obj; if (__instance.CanDragFromSlot(__instance.draggedSlot) && (Object)(object)__instance.HoveredSlot != (Object)null && __instance.CanCashBeDraggedIntoSlot(__instance.HoveredSlot) && val2 != null && val3 != null && !val2.IsLocked && !val2.IsAddLocked) { MethodInfo method = ((object)val2).GetType().GetMethod("DoesItemMatchHardFilters"); MethodInfo method2 = ((object)val2).GetType().GetMethod("DoesItemMatchPlayerFilters"); if (method != null && method2 != null) { bool flag2 = (bool)method.Invoke(val2, new object[1] { val3 }); bool flag3 = (bool)method2.Invoke(val2, new object[1] { val3 }); flag = flag2 && flag3; } else { MethodInfo method3 = ((object)val2).GetType().GetMethod("DoesItemMatchFilters"); if (method3 != null) { flag = (bool)method3.Invoke(val2, new object[1] { val3 }); } } } if (flag) { if (Utils.Is<HotbarSlot>(__instance.HoveredSlot.assignedSlot, out var _) && !Utils.Is<CashSlot>(__instance.HoveredSlot.assignedSlot, out var result3)) { __instance.HoveredSlot = (ItemSlotUI)(object)((Component)Singleton<HUD>.Instance.cashSlotUI).GetComponent<CashSlotUI>(); } float num = Mathf.Min(__instance.draggedCashAmount, val.Balance); if (num > 0f) { float num2 = num; if (__instance.HoveredSlot.assignedSlot.ItemInstance != null) { if (!Utils.Is<CashInstance>(__instance.HoveredSlot.assignedSlot.ItemInstance, out var result4)) { Logger.Error("EndCashDrag: HoveredSlot.assignedSlot.ItemInstance is not CashInstance"); } num2 = ((!Utils.Is<CashSlot>(__instance.HoveredSlot.assignedSlot, out result3)) ? Mathf.Min(num, (float)CashItemLimit - result4.Balance) : Mathf.Min(num, float.MaxValue - result4.Balance)); result4.ChangeBalance(num2); __instance.HoveredSlot.assignedSlot.ReplicateStoredInstance(); } else { if (!Utils.Is<CashInstance>(Registry.GetItem("cash").GetDefaultInstance(1), out var result5)) { Logger.Error("EndCashDrag: failed to get default CashInstance"); } result5.SetBalance(num2, false); __instance.HoveredSlot.assignedSlot.SetStoredItem((ItemInstance)(object)result5, false); } if (num2 >= val.Balance) { __instance.draggedSlot.assignedSlot.ClearStoredInstance(false); } else { val.ChangeBalance(0f - num2); __instance.draggedSlot.assignedSlot.ReplicateStoredInstance(); } } } __instance.draggedSlot.SetVisible(true); __instance.draggedSlot.UpdateUI(); __instance.draggedSlot.IsBeingDragged = false; __instance.draggedSlot = null; Object.Destroy((Object)(object)((Component)__instance.tempIcon).gameObject); Singleton<CursorManager>.Instance.SetCursorAppearance((ECursorType)0); return false; } [HarmonyPatch("UpdateCashDragAmount")] [HarmonyPrefix] private static bool UpdateCashDragAmount(ItemUIManager __instance, CashInstance instance) { float[] array = new float[3] { 50f, 10f, 1f }; float[] array2 = new float[3] { 100f, 10f, 1f }; float num = 0f; if (GameInput.MouseScrollDelta > 0f) { for (int i = 0; i < array.Length; i++) { if (__instance.draggedCashAmount >= array2[i]) { num = array[i]; break; } } } else if (GameInput.MouseScrollDelta < 0f) { for (int j = 0; j < array.Length; j++) { if (__instance.draggedCashAmount > array2[j]) { num = 0f - array[j]; break; } } } if (num != 0f) { __instance.draggedCashAmount = Mathf.Clamp(__instance.draggedCashAmount + num, 1f, Mathf.Min(instance.Balance, (float)CashItemLimit)); } return false; } [HarmonyPatch(typeof(ItemUIManager), "Update")] [HarmonyPrefix] private static bool Update(ItemUIManager __instance) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) if (GameInput.GetCurrentInputDeviceIsGamepad()) { __instance.UpdateControllerTooltip(); return false; } __instance.HoveredSlot = null; if (__instance.DraggingEnabled) { ECursorType cursorAppearance = (ECursorType)0; __instance.HoveredSlot = __instance.GetHoveredItemSlot(); if ((Object)(object)__instance.HoveredSlot != (Object)null && __instance.CanDragFromSlot(__instance.HoveredSlot)) { cursorAppearance = (ECursorType)2; } if ((Object)(object)__instance.HoveredSlot != (Object)null && (Object)(object)__instance.draggedSlot == (Object)null && __instance.HoveredSlot.assignedSlot != null && __instance.HoveredSlot.assignedSlot.Quantity > 0) { if (__instance.InfoPanel.CurrentItem != __instance.HoveredSlot.assignedSlot.ItemInstance && __instance.HoveredSlot.ShowItemInfoPanelWhenHovered()) { __instance.InfoPanel.Open(__instance.HoveredSlot.assignedSlot.ItemInstance, __instance.HoveredSlot.Rect); } } else { ItemDefinitionInfoHoverable hoveredItemInfo = __instance.GetHoveredItemInfo(); if ((Object)(object)hoveredItemInfo != (Object)null && Utils.Is<RectTransform>(((Component)hoveredItemInfo).transform, out var result)) { __instance.InfoPanel.Open(hoveredItemInfo.AssignedItem, result); } else if (__instance.InfoPanel.IsOpen) { __instance.InfoPanel.Close(); } } if ((Object)(object)__instance.draggedSlot != (Object)null) { cursorAppearance = (ECursorType)3; if (!GameInput.GetButton((ButtonCode)0) && !GameInput.GetButton((ButtonCode)1) && !GameInput.GetButton((ButtonCode)2)) { __instance.EndDrag(); } } else if ((GameInput.GetButtonDown((ButtonCode)0) || GameInput.GetButtonDown((ButtonCode)1) || GameInput.GetButtonDown((ButtonCode)2)) && (Object)(object)__instance.HoveredSlot != (Object)null) { __instance.SlotClicked(__instance.HoveredSlot); } Singleton<CursorManager>.Instance.SetCursorAppearance(cursorAppearance); } if ((Object)(object)__instance.draggedSlot != (Object)null && __instance.customDragAmount) { if (__instance.isDraggingCash && Utils.Is<CashInstance>(__instance.draggedSlot.assignedSlot.ItemInstance, out var result2)) { __instance.UpdateCashDragAmount(result2); } else if (GameInput.MouseScrollDelta > 0f) { __instance.SetDraggedAmount(Mathf.Clamp(__instance.draggedAmount + 1, 1, __instance.draggedSlot.assignedSlot.Quantity)); } else if (GameInput.MouseScrollDelta < 0f) { __instance.SetDraggedAmount(Mathf.Clamp(__instance.draggedAmount - 1, 1, __instance.draggedSlot.assignedSlot.Quantity)); } } return false; } } [HarmonyPatch(typeof(DryingRack))] internal static class DryingRackPatches { [HarmonyPatch("Awake")] [HarmonyPostfix] private static void ApplyCustomLimitAwake(DryingRack __instance) { if (__instance == null) { return; } _ = __instance.ItemCapacity; if (false) { return; } __instance.ItemCapacity = CustomStack.DryingItemCapacity.Value; if (__instance == null) { return; } DryingRackConfiguration stationConfiguration = __instance.stationConfiguration; if (stationConfiguration == null) { return; } NumberField startThreshold = stationConfiguration.StartThreshold; if (startThreshold != null) { _ = startThreshold.MaxValue; if (0 == 0) { __instance.stationConfiguration.StartThreshold.MaxValue = CustomStack.DryingItemCapacity.Value; } } } internal static void ApplyCustomLimitRuntime(int _, int current) { DryingRack[] array = Object.FindObjectsOfType<DryingRack>(); DryingRack[] array2 = array; foreach (DryingRack val in array2) { val.ItemCapacity = current; val.stationConfiguration.StartThreshold.MaxValue = current; } } } [HarmonyPatch(typeof(MixingStation))] internal static class MixingStationPatches { [HarmonyPatch("NetworkInitialize___Early")] [HarmonyPostfix] private static void ApplyCustomLimitAwake(MixingStation __instance) { if (__instance == null) { return; } _ = __instance.MaxMixQuantity; if (false) { return; } __instance.MaxMixQuantity = CustomStack.MaxMixQuantity.Value; if (__instance == null) { return; } MixingStationConfiguration stationConfiguration = __instance.stationConfiguration; if (stationConfiguration == null) { return; } NumberField startThrehold = stationConfiguration.StartThrehold; if (startThrehold != null) { _ = startThrehold.MaxValue; if (0 == 0) { __instance.stationConfiguration.StartThrehold.MaxValue = CustomStack.MaxMixQuantity.Value; } } } internal static void ApplyCustomLimitRuntime(int _, int current) { MixingStation[] array = Object.FindObjectsOfType<MixingStation>(); MixingStation[] array2 = array; foreach (MixingStation val in array2) { val.MaxMixQuantity = current; val.stationConfiguration.StartThrehold.MaxValue = current; } } } internal static class StorageVisualizerState { public class State { public bool RefreshQueued; } public static ConditionalWeakTable<StorageVisualizer, State> Table = new ConditionalWeakTable<StorageVisualizer, State>(); } [HarmonyPatch(typeof(StorageVisualizer), "QueueRefresh")] internal class QueueRefreshPatch { private static bool Prefix(StorageVisualizer __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (__instance.BlockRefreshes) { return false; } StorageVisualizerState.State orCreateValue = StorageVisualizerState.Table.GetOrCreateValue(__instance); if (orCreateValue == null) { return true; } if (orCreateValue.RefreshQueued) { Melon<CustomStack>.Logger.Debug("Queued refresh for " + ((__instance != null) ? ((Object)__instance).name : null) + ". Skipping."); return false; } orCreateValue.RefreshQueued = true; Melon<CustomStack>.Logger.Debug("Starting delayed refresh for " + ((__instance != null) ? ((Object)__instance).name : null)); MelonCoroutines.Start(DelayedRefresh(__instance)); return false; } private static IEnumerator DelayedRefresh(StorageVisualizer instance) { yield return null; StorageVisualizerState.State state = StorageVisualizerState.Table.GetOrCreateValue(instance); if (state != null) { state.RefreshQueued = false; if ((Object)(object)instance != (Object)null) { instance.RefreshVisuals(); } } } } } namespace CustomStack.Helpers { public static class MelonLoggerExtensions { public static void Debug(this Instance logger, string message, bool stacktrace = true) { MelonDebug.Msg(stacktrace ? ("[" + GetCallerInfo() + "] " + message) : message); } private static string GetCallerInfo() { StackTrace stackTrace = new StackTrace(); for (int i = 2; i < stackTrace.FrameCount; i++) { StackFrame frame = stackTrace.GetFrame(i); MethodBase method = frame.GetMethod(); if (!(method?.DeclaringType == null)) { return method.DeclaringType.FullName + "." + method.Name; } } return "unknown"; } } public static class Il2CppListExtensions { public static IEnumerable<T> AsEnumerable<T>(this List<T> list) { return list ?? new List<T>(); } } public static class Utils { private static readonly Instance Logger = new Instance("CustomStack-Utils"); public static T FindObjectByName<T>(string objectName) where T : Object { try { T[] array = Resources.FindObjectsOfTypeAll<T>(); foreach (T val in array) { if (!(((Object)val).name != objectName)) { Logger.Debug("Found " + typeof(T).Name + " '" + objectName + "' directly in loaded objects"); return val; } } return default(T); } catch (Exception ex) { Logger.Error("Error finding " + typeof(T).Name + " '" + objectName + "': " + ex.Message); return default(T); } } public static List<T> GetAllComponentsInChildrenRecursive<T>(GameObject obj) where T : Component { List<T> list = new List<T>(); if ((Object)(object)obj == (Object)null) { return list; } T[] components = obj.GetComponents<T>(); if (components.Length != 0) { list.AddRange(components); } for (int i = 0; i < obj.transform.childCount; i++) { Transform child = obj.transform.GetChild(i); list.AddRange(GetAllComponentsInChildrenRecursive<T>(((Component)child).gameObject)); } return list; } public static bool Is<T>(object obj, out T result) where T : class { if (obj is T val) { result = val; return true; } result = null; return false; } public static IEnumerator WaitForPlayer(IEnumerator routine) { while ((Object)(object)Player.Local == (Object)null || (Object)(object)((Component)Player.Local).gameObject == (Object)null) { yield return null; } MelonCoroutines.Start(routine); } public static IEnumerator WaitForNetwork(IEnumerator routine) { while (!InstanceFinder.IsServer && !InstanceFinder.IsClient) { yield return null; } MelonCoroutines.Start(routine); } public static IEnumerator WaitForNotNull(object? obj, float timeout = float.NaN, Action onTimeout = null, Action onFinish = null) { float startTime = Time.time; while (obj == null) { if (!float.IsNaN(timeout) && Time.time - startTime > timeout) { onTimeout?.Invoke(); yield break; } yield return null; } onFinish?.Invoke(); } public static IEnumerator WaitForNetworkSingleton<T>(IEnumerator coroutine) where T : NetworkSingleton<T> { while (!NetworkSingleton<T>.InstanceExists) { yield return null; } yield return coroutine; } } }