diff --git a/gradle.properties b/gradle.properties index 45150e4a8..aa1385da5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ author = masa mod_file_name = itemscroller-fabric # Current mod version -mod_version = 0.23.999-sakura.4 +mod_version = 0.23.999-sakura.5 # Required malilib version malilib_version = 0.19.999-sakura.8 diff --git a/src/main/java/fi/dy/masa/itemscroller/config/Configs.java b/src/main/java/fi/dy/masa/itemscroller/config/Configs.java index 2ab380175..a3e42e741 100644 --- a/src/main/java/fi/dy/masa/itemscroller/config/Configs.java +++ b/src/main/java/fi/dy/masa/itemscroller/config/Configs.java @@ -45,6 +45,8 @@ public static class Generic public static final ConfigBoolean SLOT_POSITION_AWARE_SCROLL_DIRECTION = new ConfigBoolean("useSlotPositionAwareScrollDirection", false, "itemscroller.config.generic.comment.useSlotPositionAwareScrollDirection").translatedName("itemscroller.config.generic.name.useSlotPositionAwareScrollDirection"); public static final ConfigBoolean VILLAGER_TRADE_USE_GLOBAL_FAVORITES = new ConfigBoolean("villagerTradeUseGlobalFavorites", true, "itemscroller.config.generic.comment.villagerTradeUseGlobalFavorites").translatedName("itemscroller.config.generic.name.villagerTradeUseGlobalFavorites"); public static final ConfigBoolean VILLAGER_TRADE_LIST_REMEMBER_SCROLL = new ConfigBoolean("villagerTradeListRememberScrollPosition", true, "itemscroller.config.generic.comment.villagerTradeListRememberScrollPosition").translatedName("itemscroller.config.generic.name.villagerTradeListRememberScrollPosition"); + public static final ConfigBoolean SORT_ASSUME_EMPTY_BOX_STACKS = new ConfigBoolean("sortAssumeEmptyBoxStacks", true, "itemscroller.config.generic.comment.sortAssumeEmptyBoxStacks").translatedName("itemscroller.config.generic.name.sortAssumeEmptyBoxStacks"); + public static final ConfigBoolean SORT_SHULKER_BOXES_AT_END = new ConfigBoolean("sortShulkerBoxesAtEnd", true, "itemscroller.config.generic.comment.sortShulkerBoxesAtEnd").translatedName("itemscroller.config.generic.name.sortShulkerBoxesAtEnd"); public static final ImmutableList OPTIONS = ImmutableList.of( CARPET_CTRL_Q_CRAFTING, @@ -65,7 +67,10 @@ public static class Generic SLOT_POSITION_AWARE_SCROLL_DIRECTION, USE_RECIPE_CACHING, VILLAGER_TRADE_USE_GLOBAL_FAVORITES, - VILLAGER_TRADE_LIST_REMEMBER_SCROLL + VILLAGER_TRADE_LIST_REMEMBER_SCROLL, + + SORT_ASSUME_EMPTY_BOX_STACKS, + SORT_SHULKER_BOXES_AT_END ); } diff --git a/src/main/java/fi/dy/masa/itemscroller/config/Hotkeys.java b/src/main/java/fi/dy/masa/itemscroller/config/Hotkeys.java index c2225c651..af09ad163 100644 --- a/src/main/java/fi/dy/masa/itemscroller/config/Hotkeys.java +++ b/src/main/java/fi/dy/masa/itemscroller/config/Hotkeys.java @@ -50,6 +50,8 @@ public class Hotkeys public static final ConfigHotkey MODIFIER_MOVE_STACK = new ConfigHotkey("modifierMoveStack", "LEFT_SHIFT", GUI_NO_ORDER, "itemscroller.config.hotkeys.comment.modifierMoveStack").translatedName("itemscroller.config.hotkeys.name.modifierMoveStack"); public static final ConfigHotkey MODIFIER_TOGGLE_VILLAGER_GLOBAL_FAVORITE = new ConfigHotkey("modifierToggleVillagerGlobalFavorite", "LEFT_SHIFT", GUI_RELAXED, "itemscroller.config.hotkeys.comment.modifierToggleVillagerGlobalFavorite").translatedName("itemscroller.config.hotkeys.name.modifierToggleVillagerGlobalFavorite"); + public static final ConfigHotkey SORT_INVENTORY = new ConfigHotkey("sortInventory", "R", GUI_NO_ORDER, "itemscroller.config.hotkeys.comment.sortInventory").translatedName("itemscroller.config.hotkeys.name.sortInventory"); + public static final List HOTKEY_LIST = ImmutableList.of( OPEN_CONFIG_GUI, TOGGLE_MOD_ON_OFF, @@ -87,6 +89,8 @@ public class Hotkeys KEY_WS_MOVE_UP_LEAVE_ONE, KEY_WS_MOVE_UP_MATCHING, KEY_WS_MOVE_UP_SINGLE, - KEY_WS_MOVE_UP_STACKS + KEY_WS_MOVE_UP_STACKS, + + SORT_INVENTORY ); } diff --git a/src/main/java/fi/dy/masa/itemscroller/event/KeybindCallbacks.java b/src/main/java/fi/dy/masa/itemscroller/event/KeybindCallbacks.java index c9b4bcf11..0ccf49ac0 100644 --- a/src/main/java/fi/dy/masa/itemscroller/event/KeybindCallbacks.java +++ b/src/main/java/fi/dy/masa/itemscroller/event/KeybindCallbacks.java @@ -1,8 +1,14 @@ package fi.dy.masa.itemscroller.event; +import fi.dy.masa.itemscroller.mixin.IMixinCraftingResultSlot; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.inventory.RecipeInputInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; +import net.minecraft.recipe.Recipe; +import net.minecraft.screen.CraftingScreenHandler; import net.minecraft.screen.slot.Slot; import fi.dy.masa.malilib.config.options.ConfigHotkey; import fi.dy.masa.malilib.gui.GuiBase; @@ -164,6 +170,11 @@ else if (key == Hotkeys.SLOT_DEBUG.getKeybind()) return true; } + else if (key == Hotkeys.SORT_INVENTORY.getKeybind()) + { + InventoryUtils.sortInventory(gui); + return true; + } return false; } diff --git a/src/main/java/fi/dy/masa/itemscroller/mixin/MixinClientPlayNetworkHandler.java b/src/main/java/fi/dy/masa/itemscroller/mixin/MixinClientPlayNetworkHandler.java new file mode 100644 index 000000000..18cf7c0b8 --- /dev/null +++ b/src/main/java/fi/dy/masa/itemscroller/mixin/MixinClientPlayNetworkHandler.java @@ -0,0 +1,22 @@ +package fi.dy.masa.itemscroller.mixin; + +import fi.dy.masa.itemscroller.util.InventoryUtils; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.packet.s2c.play.StatisticsS2CPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayNetworkHandler.class) +public class MixinClientPlayNetworkHandler +{ + @Inject(method = "onStatistics", at = @At("RETURN"), cancellable = true) + private void onPong(StatisticsS2CPacket packet, CallbackInfo ci) + { + if (InventoryUtils.onPong(packet)) + { + ci.cancel(); + } + } +} diff --git a/src/main/java/fi/dy/masa/itemscroller/mixin/MixinItemStak.java b/src/main/java/fi/dy/masa/itemscroller/mixin/MixinItemStak.java new file mode 100644 index 000000000..44e22cde8 --- /dev/null +++ b/src/main/java/fi/dy/masa/itemscroller/mixin/MixinItemStak.java @@ -0,0 +1,33 @@ +package fi.dy.masa.itemscroller.mixin; + +import fi.dy.masa.itemscroller.config.Configs; +import fi.dy.masa.itemscroller.util.InventoryUtils; +import net.minecraft.client.MinecraftClient; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ItemStack.class) +public class MixinItemStak +{ + @Inject(method = "capCount", at = @At("HEAD"), cancellable = true) + private void dontCap(int maxCount, CallbackInfo ci) { + // Client-side fx for empty shulker box stacking + if (MinecraftClient.getInstance().isOnThread() && Configs.Generic.SORT_ASSUME_EMPTY_BOX_STACKS.getBooleanValue()) + { + ci.cancel(); + } + } + + @Inject(method = "getMaxCount", at = @At("HEAD"), cancellable = true) + private void getMaxCount(CallbackInfoReturnable cir) { + // Client-side fx for empty shulker box stacking + if (MinecraftClient.getInstance().isOnThread() && Configs.Generic.SORT_ASSUME_EMPTY_BOX_STACKS.getBooleanValue() && InventoryUtils.assumeEmptyShulkerStacking) + { + cir.setReturnValue(InventoryUtils.stackMaxSize((ItemStack) (Object) this, true)); + } + } +} diff --git a/src/main/java/fi/dy/masa/itemscroller/mixin/MixinRecipeBookWidget.java b/src/main/java/fi/dy/masa/itemscroller/mixin/MixinRecipeBookWidget.java new file mode 100644 index 000000000..d5a98e459 --- /dev/null +++ b/src/main/java/fi/dy/masa/itemscroller/mixin/MixinRecipeBookWidget.java @@ -0,0 +1,9 @@ +package fi.dy.masa.itemscroller.mixin; + +import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(RecipeBookWidget.class) +public class MixinRecipeBookWidget +{ +} diff --git a/src/main/java/fi/dy/masa/itemscroller/recipes/RecipePattern.java b/src/main/java/fi/dy/masa/itemscroller/recipes/RecipePattern.java index 30599d57d..7d8794b6c 100644 --- a/src/main/java/fi/dy/masa/itemscroller/recipes/RecipePattern.java +++ b/src/main/java/fi/dy/masa/itemscroller/recipes/RecipePattern.java @@ -1,22 +1,33 @@ package fi.dy.masa.itemscroller.recipes; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Arrays; + +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtList; +import net.minecraft.recipe.CraftingRecipe; +import net.minecraft.recipe.Recipe; +import net.minecraft.recipe.RecipeEntry; +import net.minecraft.recipe.RecipeType; +import net.minecraft.recipe.input.CraftingRecipeInput; +import net.minecraft.recipe.input.RecipeInput; import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.screen.ScreenHandler; import net.minecraft.screen.slot.Slot; import fi.dy.masa.itemscroller.recipes.CraftingHandler.SlotRange; import fi.dy.masa.itemscroller.util.Constants; import fi.dy.masa.itemscroller.util.InventoryUtils; +import net.minecraft.world.World; public class RecipePattern { private ItemStack result = InventoryUtils.EMPTY_STACK; private ItemStack[] recipe = new ItemStack[9]; + private RecipeEntry vanillaRecipe; public RecipePattern() { @@ -35,6 +46,7 @@ public void clearRecipe() { Arrays.fill(this.recipe, InventoryUtils.EMPTY_STACK); this.result = InventoryUtils.EMPTY_STACK; + this.vanillaRecipe = null; } public void ensureRecipeSizeAndClearRecipe(int size) @@ -43,6 +55,19 @@ public void ensureRecipeSizeAndClearRecipe(int size) this.clearRecipe(); } + private void lookupVanillaRecipe(World world) { + this.vanillaRecipe = null; + var mc = MinecraftClient.getInstance(); + for (RecipeEntry match : mc.world.getRecipeManager().getAllMatches(RecipeType.CRAFTING, CraftingRecipeInput.create(3, 3, Arrays.asList(recipe)), world)) + { + if (InventoryUtils.areStacksEqual(result, match.value().getResult(world.getRegistryManager()))) + { + this.vanillaRecipe = match; + return; + } + } + } + public void storeCraftingRecipe(Slot slot, HandledScreen gui, boolean clearIfEmpty) { SlotRange range = CraftingHandler.getCraftingGridSlots(gui, slot); @@ -63,6 +88,7 @@ public void storeCraftingRecipe(Slot slot, HandledScreen getVanillaRecipeEntry() + { + return vanillaRecipe; + } + + @SuppressWarnings("unchecked") + @Nullable + public Recipe getVanillaRecipe() + { + if (vanillaRecipe == null) + { + return null; + } + return (Recipe) vanillaRecipe.value(); + } } diff --git a/src/main/java/fi/dy/masa/itemscroller/util/InventoryUtils.java b/src/main/java/fi/dy/masa/itemscroller/util/InventoryUtils.java index 5623e7027..a4598c387 100644 --- a/src/main/java/fi/dy/masa/itemscroller/util/InventoryUtils.java +++ b/src/main/java/fi/dy/masa/itemscroller/util/InventoryUtils.java @@ -3,9 +3,13 @@ import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.util.*; + +import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntComparator; +import it.unimi.dsi.fastutil.ints.IntIntMutablePair; +import net.minecraft.block.ShulkerBoxBlock; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; @@ -14,12 +18,21 @@ import net.minecraft.client.gui.screen.ingame.MerchantScreen; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.world.ClientWorld; +import net.minecraft.component.ComponentMap; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.ContainerComponent; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.CraftingResultInventory; import net.minecraft.inventory.Inventory; import net.minecraft.inventory.RecipeInputInventory; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.network.packet.c2s.play.ClientStatusC2SPacket; +import net.minecraft.network.packet.c2s.query.QueryPingC2SPacket; +import net.minecraft.network.packet.s2c.play.StatisticsS2CPacket; +import net.minecraft.network.packet.s2c.query.PingResultS2CPacket; import net.minecraft.recipe.CraftingRecipe; import net.minecraft.recipe.RecipeEntry; import net.minecraft.recipe.RecipeType; @@ -32,6 +45,7 @@ import net.minecraft.screen.slot.SlotActionType; import net.minecraft.screen.slot.TradeOutputSlot; import net.minecraft.util.Identifier; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.village.TradeOffer; import net.minecraft.village.TradeOfferList; import net.minecraft.world.GameRules; @@ -52,6 +66,7 @@ public class InventoryUtils { private static final Set DRAGGED_SLOTS = new HashSet<>(); + private static final int SERVER_SYNC_MAGIC = 45510; private static WeakReference sourceSlotCandidate = null; private static WeakReference sourceSlot = null; @@ -62,6 +77,8 @@ public class InventoryUtils private static int lastPosY; private static int slotNumberLast; private static boolean inhibitCraftResultUpdate; + private static Runnable selectedSlotUpdateTask; + public static boolean assumeEmptyShulkerStacking = false; public static void setInhibitCraftingOutputUpdate(boolean inhibitUpdate) { @@ -1419,7 +1436,7 @@ private static boolean tryMoveItemsToCraftingGridSlots(RecipePattern recipe, for (Map.Entry entry : ingredientSlots.entrySet()) { - ItemStack ingredientReference = entry.getKey().getStack(); + ItemStack ingredientReference = entry.getKey().stack(); IntArrayList recipeSlots = entry.getValue(); IntArrayList targetSlots = new IntArrayList(); @@ -2566,8 +2583,11 @@ public static void tryClearCursor(HandledScreen gui) break; } - leftClickSlot(gui, slotNum); - stackCursor = gui.getScreenHandler().getCursorStack(); + if (slot.inventory instanceof PlayerInventory) + { + leftClickSlot(gui, slotNum); + stackCursor = gui.getScreenHandler().getCursorStack(); + } } } } @@ -2589,6 +2609,283 @@ public static MoveAction getActiveMoveAction() return activeMoveAction; } + public static void sortInventory(HandledScreen gui) + { + Pair range = new IntIntMutablePair(Integer.MAX_VALUE, 0); + Slot focusedSlot = AccessorUtils.getSlotUnderMouse(gui); + if (focusedSlot == null) { + return; + } + ScreenHandler container = gui.getScreenHandler(); + if (gui instanceof CreativeInventoryScreen creative && !creative.isInventoryTabSelected()) + { + return; + } + + int focusedIndex = -1; + for (int i = 0; i < container.slots.size(); i++) + { + Slot slot = container.slots.get(i); + if (slot == focusedSlot) + { + focusedIndex = i; + } + if (slot.inventory == focusedSlot.inventory) + { + if (i < range.first()) + { + range.first(i); + } + if (i >= range.second()) + { + range.second(i + 1); + } + } + } + if (focusedIndex == -1) + { + return; + } + if (focusedSlot.inventory instanceof PlayerInventory) + { + if (range.left() == 5 && range.right() == 46) + { + // Creative, PlayerScreenHandler + if (focusedIndex >= 9 && focusedIndex < 36) + { + range.left(9).right(36); + } + else if (focusedIndex >= 36 && focusedIndex < 45) + { + range.left(36).right(45); + } + } + else if (range.right() - range.left() == 36) + { + // Normal containers + if (focusedIndex < range.left() + 27) + { + range.right(range.left() + 27); + } + else + { + range.left(range.right() - 9); + } + } + } + + System.out.printf("Sorting [%d, %d)\n", range.first(), range.second()); + tryClearCursor(gui); + tryMergeItems(gui, range.left(), range.right() - 1); + + if (Configs.Generic.SORT_ASSUME_EMPTY_BOX_STACKS.getBooleanValue()) + { + ClientStatusC2SPacket packet = new ClientStatusC2SPacket(ClientStatusC2SPacket.Mode.REQUEST_STATS); + MinecraftClient.getInstance().getNetworkHandler().sendPacket(packet); + selectedSlotUpdateTask = () -> quickSort(gui, range.first(), range.second() - 1); + } + else + { + quickSort(gui, range.first(), range.second() - 1); + } + } + + /** + * sort inventory + */ + private static void quickSort(HandledScreen gui, int start, int end) + { + if (start >= end) return; + + ItemStack mid = gui.getScreenHandler().getSlot(end).getStack(); + int l = start; + int r = end - 1; + while (l < r) + { + while (l < r && compareStacks(gui.getScreenHandler().getSlot(l).getStack(), mid) < 0) + { + l++; + } + while (l < r && compareStacks(gui.getScreenHandler().getSlot(r).getStack(), mid) >= 0) + { + r--; + } + if (l != r) + { + swapSlots(gui, l, r); + } + } + if (compareStacks(gui.getScreenHandler().getSlot(l).getStack(), gui.getScreenHandler().getSlot(end).getStack()) >= 0) + { + swapSlots(gui, l, end); + } + else + { + l++; + } + + quickSort(gui, start, l - 1); + quickSort(gui, l + 1, end); + } + + private static int compareStacks(ItemStack stack1, ItemStack stack2) + { + if (Configs.Generic.SORT_SHULKER_BOXES_AT_END.getBooleanValue()) + { + if (isShulkerBox(stack1) && !isShulkerBox(stack2)) + { + return 1; + } + if (!isShulkerBox(stack1) && isShulkerBox(stack2)) + { + return -1; + } + } + if (stack1.isEmpty() && stack2.isEmpty()) + return 0; + else if (stack1.isEmpty()) + return 1; + else if (stack2.isEmpty()) + return -1; + + if (isShulkerBox(stack1) && isShulkerBox(stack2)) + { + List contents1 = stack1.getOrDefault(DataComponentTypes.CONTAINER, ContainerComponent.DEFAULT).streamNonEmpty().toList(); + List contents2 = stack2.getOrDefault(DataComponentTypes.CONTAINER, ContainerComponent.DEFAULT).streamNonEmpty().toList(); + if (contents1.size() != contents2.size()) + { + return Integer.compare(contents1.size(), contents2.size()); + } + } + if (stack1.getItem() != stack2.getItem()) + { + return Integer.compare(Registries.ITEM.getRawId(stack1.getItem()), Registries.ITEM.getRawId(stack2.getItem())); + } + if (!areStacksEqual(stack1, stack2)) + { + return Integer.compare(stack1.getComponents().hashCode(), stack2.getComponents().hashCode()); + } + return Integer.compare(-stack1.getCount(), -stack2.getCount()); + } + + public static boolean onPong(StatisticsS2CPacket packet) + { + if (selectedSlotUpdateTask != null) + { + selectedSlotUpdateTask.run(); + selectedSlotUpdateTask = null; + return true; + } + return false; + } + + private static boolean isShulkerBox(ItemStack stack) + { + return stack.getItem() instanceof BlockItem bi && bi.getBlock() instanceof ShulkerBoxBlock; + } + + private static boolean isEmptyShulkerBox(ItemStack stack) + { + return isShulkerBox(stack) && stack.getOrDefault(DataComponentTypes.CONTAINER, ContainerComponent.DEFAULT).streamNonEmpty().findAny().isEmpty(); + } + + public static int stackMaxSize(ItemStack stack, boolean assumeShulkerStacking) { + if (stack.isEmpty()) { + return 64; + } + + if (assumeShulkerStacking && Configs.Generic.SORT_ASSUME_EMPTY_BOX_STACKS.getBooleanValue()) + { + if (isEmptyShulkerBox(stack)) + { + return 64; + } + } + + return stack.getOrDefault(DataComponentTypes.MAX_STACK_SIZE, 1); + } + + /** + * @return are there still items left in the original slot? + */ + private static boolean addStackTo(HandledScreen gui, Slot slot, Slot target) + { + if (slot == null || target == null) + { + return false; + } + + ItemStack stack = slot.getStack(); + ItemStack targetStack = target.getStack(); + + if (stack.isEmpty() || !ItemStack.areItemsEqual(stack, targetStack)) + { + return !stack.isEmpty(); + } + + if (targetStack.isEmpty()) + { + clickSlot(gui, slot, slot.id, 0, SlotActionType.PICKUP); + clickSlot(gui, target, target.id, 0, SlotActionType.PICKUP); + System.out.printf("Moved stack from slot %d to slot %d\n", slot.id, target.id); + return false; + } + + int stackSize = stack.getCount(); + int targetSize = targetStack.getCount(); + assumeEmptyShulkerStacking = true; + int maxSize = stackMaxSize(stack, true); + System.out.printf("Merging %s into %s, maxSize: %d\n", stack, targetStack, maxSize); + + if (targetSize >= maxSize) + { + return true; + } + + clickSlot(gui, slot, slot.id, 0, SlotActionType.PICKUP); + clickSlot(gui, target, target.id, 0, SlotActionType.PICKUP); + clickSlot(gui, slot, slot.id, 0, SlotActionType.PICKUP); + assumeEmptyShulkerStacking = false; + int amount = stackSize + targetSize - maxSize; + + return amount > 0; + } + + private static void tryMergeItems(HandledScreen gui, int left, int right) + { + Map nonFullStacks = new HashMap<>(); + + for (int i = left; i <= right; i++) + { + Slot slot = gui.getScreenHandler().getSlot(i); + + if (slot.hasStack()) + { + ItemStack stack = slot.getStack(); + + if (stack.getCount() >= stackMaxSize(stack, true)) { + // ignore overstacking items. + continue; + } + + ItemType key = new ItemType(stack); + int slotNum = nonFullStacks.getOrDefault(key, -1); + + if (slotNum == -1) + { + nonFullStacks.put(key, i); + } + else + { + if (addStackTo(gui, slot, gui.getScreenHandler().getSlot(slotNum))) + { + nonFullStacks.put(key, i); + } + } + } + } + } + /* private static class SlotVerticalSorterSlots implements Comparator { diff --git a/src/main/java/fi/dy/masa/itemscroller/util/ItemType.java b/src/main/java/fi/dy/masa/itemscroller/util/ItemType.java index 86bb65538..f83c22a48 100644 --- a/src/main/java/fi/dy/masa/itemscroller/util/ItemType.java +++ b/src/main/java/fi/dy/masa/itemscroller/util/ItemType.java @@ -11,20 +11,13 @@ * Wrapper class for ItemStack, which implements equals() * for the item, damage and NBT, but not stackSize. */ -public class ItemType +public record ItemType(ItemStack stack) { - private final ItemStack stack; - public ItemType(@Nonnull ItemStack stack) { this.stack = stack.isEmpty() ? InventoryUtils.EMPTY_STACK : InventoryUtils.copyStack(stack, false); } - public ItemStack getStack() - { - return this.stack; - } - @Override public int hashCode() { @@ -53,6 +46,7 @@ public boolean equals(Object obj) /** * Returns a map that has a list of the indices for each different item in the input list + * * @param stacks * @return */ diff --git a/src/main/resources/assets/itemscroller/lang/en_us.json b/src/main/resources/assets/itemscroller/lang/en_us.json index 60f56d59f..0d3c24524 100644 --- a/src/main/resources/assets/itemscroller/lang/en_us.json +++ b/src/main/resources/assets/itemscroller/lang/en_us.json @@ -64,6 +64,10 @@ "itemscroller.config.toggles.comment.enableShiftDropItems": "Enables dropping all matching items at once by holding\nshift while clicking to drop a stack", "itemscroller.config.toggles.comment.enableShiftPlaceItems": "Enables moving all matching stacks at once by holding\nshift while placing items to an empty slot", "itemscroller.config.toggles.comment.enableVillagerTradeFeatures": "Enable trade favoriting and quick trade features for villagers.\nNote: The Shift + scrolling over the output slot is a separate feature\nand not affected by this option.\nThis option enables middle clicking to mark favorite trades,\nand right clicking on the trade list to fully trade that one trade.", + "itemscroller.config.generic.comment.massCraftUseRecipeBook": "Use the recipe book protocol for mass crafting.\nThis is faster than the old method.", + "itemscroller.config.hotkeys.comment.sortInventory": "Sort the inventory that the cursor is hovering over", + "itemscroller.config.generic.comment.sortAssumeEmptyBoxStacks": "Assume that empty boxes are stacking items\nwhen sorting the inventory.\nThis is useful if you installed mods to stack\nshulker boxes on remote server.\nThis will send an extra packet to ensure the\ninventory is synced.", + "itemscroller.config.generic.comment.sortShulkerBoxesAtEnd": "Sort shulker boxes at the end of the inventory\nwhen sorting the inventory.", "itemscroller.config.hotkeys.name.openConfigGui": "openConfigGui", "itemscroller.config.hotkeys.name.craftEverything": "craftEverything", @@ -141,4 +145,4 @@ "itemscroller.message.toggled_mod_off": "Toggled all Item Scroller functionality §cOFF", "itemscroller.message.toggled_mod_on": "Toggled all Item Scroller functionality §aON" -} \ No newline at end of file +} diff --git a/src/main/resources/assets/itemscroller/lang/zh_cn.json b/src/main/resources/assets/itemscroller/lang/zh_cn.json index 82dd1311a..9f32e9fd6 100644 --- a/src/main/resources/assets/itemscroller/lang/zh_cn.json +++ b/src/main/resources/assets/itemscroller/lang/zh_cn.json @@ -38,6 +38,10 @@ "itemscroller.config.generic.comment.useSlotPositionAwareScrollDirection": "启用后,物品的移动方向将取决于屏幕上栏位的y轴位置。\n可能导致更复杂的背包,谨慎使用!", "itemscroller.config.generic.comment.villagerTradeUseGlobalFavorites": "启用后,收藏的交易项目将会被置顶。", "itemscroller.config.generic.comment.villagerTradeListRememberScrollPosition": "启用后,退出村民交易GUI将不会重置滚动条。", + "itemscroller.config.generic.comment.massCraftUseRecipeBook": "使用原版配方书协议进行批量合成。这使得合成速度更快。", + "itemscroller.config.hotkeys.comment.sortInventory": "对鼠标指针所在的物品栏进行整理。", + "itemscroller.config.generic.comment.sortAssumeEmptyBoxStacks": "整理物品栏时假设空潜影盒可以堆叠。\n当你在远程服务器启用此特性时会很有用。\n这会多发送一个网络包来确保服务器同步", + "itemscroller.config.generic.comment.sortShulkerBoxesAtEnd": "整理物品栏时将潜影盒放在最后。", "itemscroller.config.toggles.name.enableCraftingFeatures": "启用配方视图", "itemscroller.config.toggles.name.enableDropkeyDropMatching": "全部丢弃", @@ -141,4 +145,4 @@ "itemscroller.message.toggled_mod_off": "Item Scroller 功能调整为:§c关闭", "itemscroller.message.toggled_mod_on": "Item Scroller 功能调整为:§a开启" -} \ No newline at end of file +} diff --git a/src/main/resources/mixins.itemscroller.json b/src/main/resources/mixins.itemscroller.json index 309bd275b..69e662dbe 100644 --- a/src/main/resources/mixins.itemscroller.json +++ b/src/main/resources/mixins.itemscroller.json @@ -10,10 +10,13 @@ "IMixinSlot", "MixinAbstractInventoryScreen", "MixinClientPlayerInteractionManager", + "MixinClientPlayNetworkHandler", "MixinCraftingScreenHandler", "MixinMerchantScreen", "MixinMerchantScreenHandler", - "MixinScreen" + "MixinRecipeBookWidget", + "MixinScreen", + "MixinItemStak" ], "injectors": { "defaultRequire": 1