Unlimited Storage

Unlimited Storage

0 Downloads

No way for other mods to detect extra pages

besworks opened this issue ยท 2 comments

commented

I have created a mod that allows manually organizing chests. A user has reported that it is incompatible with your mod. The item picked up is always from the first page no matter what page is visible.

I looked to see if I could add support but I don't see any way to detect the ModState.Offset property. Would you consider adding this as a public property of ItemGrabMenu or InventoryMenu so that I can access it?

commented

I was able to patch compatibility in using Harmony and Reflection. It would be cleaner if you exposed the ModState.Offset property but it's not necessary.

I also had to patch over your GetMaxOffset Extension onto InventoryMenu. My mod expands the inventory with nulls to allow items to be placed in any slot and you were using GetItemsForPlayer in your calculation which filters out nulls.

I don't think there's any action needed on your part to close this issue.

commented

Here's my patches. I know they're not very DRY. I never bothered cleaning up my copy-pasta after getting it working. That's a whole lotta code to just grab one value and inject another. I think even if you rounded up the MaxOffset total I would still need to hook into your code anyway though, because I need to add the extra nulls to the chest every time a row is added. Honestly, I don't think you really need to put any effort into this. My workarounds are performing pretty effectively.

using HarmonyLib;
using StardewModdingAPI;
using StardewValley;
using StardewValley.Inventories;
using StardewValley.Menus;
using StardewValley.Objects;
using System;
using System.Reflection;

namespace ManuallyOrganizeChests.UnlimitedStorageIntegration
{
    internal class ModStateOffsetPatch
    {
        static public MethodBase? TargetMethod()
        {
            try
            {
                var assembly = AppDomain.CurrentDomain.GetAssemblies()
                    .FirstOrDefault(a => a.GetName().Name == "UnlimitedStorage");

                if (assembly == null) return null;

                var modPatchesType = assembly.GetType("LeFauxMods.UnlimitedStorage.Services.ModPatches");
                if (modPatchesType == null) return null;

                return AccessTools.Method(
                    modPatchesType,
                    "TryAdjustInventory",
                    new[] { typeof(InventoryMenu), typeof(IInventory).MakeByRefType() });
            }
            catch (Exception ex)
            {
                ModEntry.ModMonitor?.Log($"Failed patching TryAdjustInventory: {ex.Message}", LogLevel.Error);
                return null;
            }
        }

        public static void Postfix(InventoryMenu __instance, ref IInventory? __state)
        {
            try
            {
                var assembly = AppDomain.CurrentDomain.GetAssemblies()
                    .FirstOrDefault(a => a.GetName().Name == "UnlimitedStorage");
                if (assembly == null) return;

                var modStateType = assembly.GetType("LeFauxMods.UnlimitedStorage.Services.ModState");
                if (modStateType == null) return;

                var instanceField = modStateType.GetField("Instance", 
                    BindingFlags.NonPublic | BindingFlags.Static);
                if (instanceField?.GetValue(null) is object instance)
                {
                    var offsetField = modStateType.GetField("offset", 
                        BindingFlags.NonPublic | BindingFlags.Instance);
                    var perScreen = offsetField?.GetValue(instance);
                    if (perScreen?.GetType().GetProperty("Value")?.GetValue(perScreen) is int offset)
                    {
                        ModEntry.UnlimitedStorageOffset = offset;
                    }
                }
            }
            catch (Exception ex)
            {
                ModEntry.ModMonitor?.Log($"Failed to capture offset: {ex.Message}", LogLevel.Error);
            }
        }
    }

    internal class GetMaxOffsetPatch
    {
        static public MethodBase? TargetMethod()
        {
            try
            {
                var assembly = AppDomain.CurrentDomain.GetAssemblies()
                    .FirstOrDefault(a => a.GetName().Name == "UnlimitedStorage");

                if (assembly == null)
                    return null;

                var modExtensionsType = assembly.GetType("LeFauxMods.UnlimitedStorage.Utilities.ModExtensions");
                return modExtensionsType?.GetMethod("GetMaxOffset",
                    BindingFlags.Public | BindingFlags.Static);
            }
            catch (Exception ex)
            {
                ModEntry.ModMonitor?.Log($"Error finding GetMaxOffset method: {ex}", LogLevel.Error);
                return null;
            }
        }

        public static bool Prefix(InventoryMenu __instance, Chest chest, ref int __result)
        {
            try
            {
                int baseRows;
                int columnsPerRow;
                switch (chest.SpecialChestType)
                {
                    case Chest.SpecialChestTypes.BigChest:
                        baseRows = 5;
                        columnsPerRow = 14;
                        break;
                    case Chest.SpecialChestTypes.MiniShippingBin:
                    case Chest.SpecialChestTypes.JunimoChest:
                        baseRows = 3;
                        columnsPerRow = 3;
                        break;
                    default:
                        baseRows = 3;
                        columnsPerRow = 12;
                        break;
                }

                // For Junimo chests, check global inventory
                int currentSize;
                if (chest.SpecialChestType == Chest.SpecialChestTypes.JunimoChest)
                {
                    var globalInventory = Game1.player.team.GetOrCreateGlobalInventory("JunimoChests");
                    currentSize = globalInventory?.Count ?? 0;
                }
                else
                {
                    currentSize = chest.Items.Count;
                }

                int highestUsedSlot = -1;
                for (int i = currentSize - 1; i >= 0; i--)
                {
                    if ((chest.SpecialChestType == Chest.SpecialChestTypes.JunimoChest && 
                         Game1.player.team.GetOrCreateGlobalInventory("JunimoChests")[i] != null) ||
                        (chest.SpecialChestType != Chest.SpecialChestTypes.JunimoChest && 
                         chest.Items[i] != null))
                    {
                        highestUsedSlot = i;
                        break;
                    }
                }

                int itemRows = highestUsedSlot >= 0 
                    ? (int)Math.Ceiling((float)(highestUsedSlot + 1) / columnsPerRow)
                    : 0;
                
                // Always show one extra row
                int totalRows = Math.Max(baseRows, itemRows + 1);
                int neededCapacity = totalRows * columnsPerRow;
                
                // Check against global inventory for expansion needs
                if (currentSize < neededCapacity)
                {
                    if (chest.SpecialChestType == Chest.SpecialChestTypes.JunimoChest)
                    {
                        var globalInventory = Game1.player.team.GetOrCreateGlobalInventory("JunimoChests");
                        while (globalInventory.Count < neededCapacity)
                        {
                            globalInventory.Add(null);
                        }
                    }
                    
                    while (chest.Items.Count < neededCapacity)
                    {
                        chest.Items.Add(null);
                    }
                }

                __result = Math.Max(0, totalRows - baseRows);
                return false;
            }
            catch (Exception ex)
            {
                ModEntry.ModMonitor?.Log($"Failed patching GetMaxOffset: {ex}", LogLevel.Error);
                return true;
            }
        }
    }
}