Skip to content

Commit 3c863f4

Browse files
committed
Add InventorySorter: sorting and stacking of containers or your own inventory
1 parent 22edecf commit 3c863f4

4 files changed

Lines changed: 213 additions & 0 deletions

File tree

src/main/java/net/wurstclient/hack/HackList.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ public final class HackList implements UpdateListener
178178
public final InstaBuildHack instaBuildHack = new InstaBuildHack();
179179
public final InstantBunkerHack instantBunkerHack = new InstantBunkerHack();
180180
public final InvWalkHack invWalkHack = new InvWalkHack();
181+
public final InventorySorterHack inventorySorterHack =
182+
new InventorySorterHack();
181183
public final ItemEspHack itemEspHack = new ItemEspHack();
182184
public final ItemGeneratorHack itemGeneratorHack = new ItemGeneratorHack();
183185
public final net.wurstclient.hacks.itemhandler.ItemHandlerHack itemHandlerHack =
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*
2+
* Copyright (c) 2014-2026 Wurst-Imperium and contributors.
3+
*
4+
* This source code is subject to the terms of the GNU General Public
5+
* License, version 3. If a copy of the GPL was not distributed with this
6+
* file, You can obtain one at: https://www.gnu.org/licenses/gpl-3.0.txt
7+
*/
8+
package net.wurstclient.hacks;
9+
10+
import java.util.ArrayList;
11+
12+
import org.lwjgl.glfw.GLFW;
13+
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
14+
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
15+
import net.minecraft.world.inventory.AbstractContainerMenu;
16+
import net.minecraft.world.inventory.ContainerInput;
17+
import net.minecraft.world.inventory.Slot;
18+
import net.minecraft.world.item.ItemStack;
19+
import net.wurstclient.Category;
20+
import net.wurstclient.SearchTags;
21+
import net.wurstclient.events.MouseButtonPressListener;
22+
import net.wurstclient.hack.Hack;
23+
import net.wurstclient.mixin.HandledScreenAccessor;
24+
import net.wurstclient.settings.CheckboxSetting;
25+
import net.wurstclient.settings.EnumSetting;
26+
27+
@SearchTags({"inventory sorter", "InventorySorter", "InventoryTweaks",
28+
"inventory tweaks", "sort inventory", "middle click sort", "auto sort"})
29+
public final class InventorySorterHack extends Hack
30+
implements MouseButtonPressListener
31+
{
32+
private final EnumSetting<SortButton> button = new EnumSetting<>("Button",
33+
"Which mouse button sorts the hovered inventory section.",
34+
SortButton.values(), SortButton.MIDDLE);
35+
36+
private final CheckboxSetting includeHotbar =
37+
new CheckboxSetting("Include hotbar",
38+
"Also sorts your hotbar when sorting your own inventory.\n"
39+
+ "When off, only the upper 27 storage slots are sorted.",
40+
false);
41+
42+
public InventorySorterHack()
43+
{
44+
super("InventorySorter");
45+
setCategory(Category.ITEMS);
46+
addSetting(button);
47+
addSetting(includeHotbar);
48+
}
49+
50+
@Override
51+
protected void onEnable()
52+
{
53+
EVENTS.add(MouseButtonPressListener.class, this);
54+
}
55+
56+
@Override
57+
protected void onDisable()
58+
{
59+
EVENTS.remove(MouseButtonPressListener.class, this);
60+
}
61+
62+
@Override
63+
public void onMouseButtonPress(MouseButtonPressEvent event)
64+
{
65+
if(event.getAction() != GLFW.GLFW_PRESS
66+
|| event.getButton() != button.getSelected().glfwButton)
67+
return;
68+
69+
// only inside a real container screen, not the creative item palette
70+
if(!(MC.gui.screen() instanceof AbstractContainerScreen<?> screen)
71+
|| MC.gui.screen() instanceof CreativeModeInventoryScreen)
72+
return;
73+
74+
Slot hovered = ((HandledScreenAccessor)screen).getHoveredSlot();
75+
if(hovered == null)
76+
return;
77+
78+
sort(screen, sectionSlots(screen, hovered));
79+
}
80+
81+
private ArrayList<Slot> sectionSlots(AbstractContainerScreen<?> screen,
82+
Slot hovered)
83+
{
84+
AbstractContainerMenu menu = screen.getMenu();
85+
boolean playerInv = hovered.container == MC.player.getInventory();
86+
87+
ArrayList<Slot> slots = new ArrayList<>();
88+
for(Slot slot : menu.slots)
89+
{
90+
if(slot.container != hovered.container)
91+
continue;
92+
93+
if(playerInv)
94+
{
95+
int idx = slot.getContainerSlot();
96+
// 0-8 hotbar, 9-35 main storage, 36+ armor/offhand
97+
boolean main = idx >= 9 && idx <= 35;
98+
boolean hotbar = idx >= 0 && idx <= 8;
99+
if(!main && !(hotbar && includeHotbar.isChecked()))
100+
continue;
101+
}
102+
103+
slots.add(slot);
104+
}
105+
106+
return slots;
107+
}
108+
109+
private void sort(AbstractContainerScreen<?> screen, ArrayList<Slot> slots)
110+
{
111+
if(slots.size() < 2)
112+
return;
113+
114+
// 1. merge partial stacks of the same item so each item ends up as a
115+
// run of full stacks plus at most one partial
116+
for(int i = 0; i < slots.size(); i++)
117+
{
118+
Slot dst = slots.get(i);
119+
if(dst.getItem().isEmpty())
120+
continue;
121+
122+
for(int j = i + 1; j < slots.size(); j++)
123+
{
124+
ItemStack dstStack = dst.getItem();
125+
if(dstStack.getCount() >= dstStack.getMaxStackSize())
126+
break;
127+
128+
Slot src = slots.get(j);
129+
if(src.getItem().isEmpty() || !ItemStack
130+
.isSameItemSameComponents(dstStack, src.getItem()))
131+
continue;
132+
133+
// pick up src, drop onto dst (fills dst), return any remainder
134+
click(screen, src);
135+
click(screen, dst);
136+
if(!screen.getMenu().getCarried().isEmpty())
137+
click(screen, src);
138+
}
139+
}
140+
141+
// 2. group items by name via a selection sort; only ever swaps two
142+
// slots
143+
// holding different items, so each swap is a clean 3-click exchange
144+
for(int a = 0; a < slots.size() - 1; a++)
145+
{
146+
int min = a;
147+
for(int b = a + 1; b < slots.size(); b++)
148+
if(compare(slots.get(b).getItem(),
149+
slots.get(min).getItem()) < 0)
150+
min = b;
151+
152+
if(min == a)
153+
continue;
154+
155+
Slot slotA = slots.get(a);
156+
Slot slotMin = slots.get(min);
157+
// same item type can't reach here as a swap target (compare == 0),
158+
// so this is always an empty<->item or itemA<->itemB exchange
159+
click(screen, slotA);
160+
click(screen, slotMin);
161+
click(screen, slotA);
162+
}
163+
}
164+
165+
private void click(AbstractContainerScreen<?> screen, Slot slot)
166+
{
167+
screen.slotClicked(slot, slot.index, 0, ContainerInput.PICKUP);
168+
}
169+
170+
private int compare(ItemStack a, ItemStack b)
171+
{
172+
boolean ae = a.isEmpty();
173+
boolean be = b.isEmpty();
174+
if(ae || be)
175+
return ae == be ? 0 : ae ? 1 : -1;
176+
177+
return sortKey(a).compareTo(sortKey(b));
178+
}
179+
180+
private String sortKey(ItemStack stack)
181+
{
182+
return stack.getHoverName().getString().toLowerCase();
183+
}
184+
185+
private enum SortButton
186+
{
187+
LEFT("Left", GLFW.GLFW_MOUSE_BUTTON_LEFT),
188+
MIDDLE("Middle", GLFW.GLFW_MOUSE_BUTTON_MIDDLE),
189+
RIGHT("Right", GLFW.GLFW_MOUSE_BUTTON_RIGHT);
190+
191+
private final String name;
192+
private final int glfwButton;
193+
194+
SortButton(String name, int glfwButton)
195+
{
196+
this.name = name;
197+
this.glfwButton = glfwButton;
198+
}
199+
200+
@Override
201+
public String toString()
202+
{
203+
return name;
204+
}
205+
}
206+
}

src/main/java/net/wurstclient/mixin/HandledScreenAccessor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@
88
package net.wurstclient.mixin;
99

1010
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
11+
import net.minecraft.world.inventory.Slot;
1112
import org.spongepowered.asm.mixin.Mixin;
1213
import org.spongepowered.asm.mixin.gen.Accessor;
1314

1415
@Mixin(AbstractContainerScreen.class)
1516
public interface HandledScreenAccessor
1617
{
18+
@Accessor("hoveredSlot")
19+
Slot getHoveredSlot();
20+
1721
@Accessor("leftPos")
1822
int getX();
1923

src/main/resources/assets/wurst/translations/en_us.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"description.wurst.hack.areanuker": "Combines Excavator's area selection with SpeedNuker's speed.",
3535
"description.wurst.hack.autospawnproofer": "Automatically places light sources (torches by default) on nearby tiles where hostile mobs could spawn.",
3636
"description.wurst.hack.fastfill": "Block area filler. Select two corners and a block, then fly around the box while FastFill places the block into every reachable empty spot. With §lReplace§r on it also breaks any wrong blocks first.",
37+
"description.wurst.hack.inventorysorter": "Click a container or your own inventory with the configured button to instantly sort and stack the section you are hovering.",
3738
"description.wurst.setting.arrowdmg.strength": "Strength of the effect. 10 is the highest possible as of Minecraft 1.21.",
3839
"description.wurst.setting.arrowdmg.trident_yeet_mode": "When enabled, tridents fly much further. Doesn't seem to affect damage or Riptide.\n\nWARNING: You can easily lose your trident by enabling this option!",
3940
"description.wurst.hack.attributeswap": "Swaps main-hand item attributes with the target slot.",

0 commit comments

Comments
 (0)