
No way for other mods to detect extra pages
besworks opened this issue ยท 2 comments
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?
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.
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;
}
}
}
}