Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ org.gradle.parallel=true
# Fabric Properties
# check these on https://fabricmc.net/develop

minecraft_version=26.1.2
loader_version=0.18.6
loom_version=1.16-SNAPSHOT
minecraft_version=26.2
loader_version=0.19.3
loom_version=1.17-SNAPSHOT

# Mod Properties
mod_version=1.1.5+26.1
mod_version=1.1.6+26.2-beta
maven_group=borknbeans.lightweightinventorysorting
archives_base_name=lightweight-inventory-sorting

# Dependencies
fabric_version=0.145.4+26.1.1
modmenu_version=18.0.0-alpha.8
fabric_version=0.152.1+26.2
modmenu_version=20.0.0-beta.2
cloth_version=26.1.154
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
import org.spongepowered.asm.mixin.Unique;

@Mixin(ContainerScreen.class)
public abstract class GenericContainerScreenMixin extends AbstractContainerScreen<ChestMenu> {
public abstract class ContainerScreenMixin extends AbstractContainerScreen<ChestMenu> {

@Unique
private SortButton sortButton;

public GenericContainerScreenMixin(ChestMenu menu, Inventory inventory, Component title) {
public ContainerScreenMixin(ChestMenu menu, Inventory inventory, Component title) {
super(menu, inventory, title);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package borknbeans.lightweightinventorysorting.mixin.client;

import borknbeans.lightweightinventorysorting.LightweightInventorySortingClient;
import borknbeans.lightweightinventorysorting.config.Config;
import borknbeans.lightweightinventorysorting.sorting.SortButton;
import borknbeans.lightweightinventorysorting.sorting.Sorter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.CreativeModeTab;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(CreativeModeInventoryScreen.class)
public abstract class CreativeModeInventoryScreenMixin extends AbstractContainerScreen<AbstractContainerMenu> {

@Unique
private SortButton sortButton;

public CreativeModeInventoryScreenMixin(AbstractContainerMenu menu, Inventory inventory, Component title) {
super(menu, inventory, title);
}

@Inject(method = "init", at = @At("TAIL"))
private void addSortButton(CallbackInfo ci) {
// Only add sort button for the inventory tab
updateSortButton();
}

@Inject(method = "selectTab", at = @At("TAIL"))
private void onTabSelected(CallbackInfo ci) {
// Update button visibility when tab changes
updateSortButton();
}

@Unique
private void updateSortButton() {
if (isInventoryTab()) {
if (sortButton == null) {
int x = this.leftPos + this.imageWidth - 20 + Config.xOffsetContainer;
int y = this.topPos + 4 + Config.yOffsetContainer;
int size = Config.buttonSize.getButtonSize();
// Creative grid is 3x9 (27 slots)
// Slots 9-35: Creative grid (3 rows × 9 columns)
// Avoids armor slots (5-8) and hotbar items (36-44)
sortButton = new SortButton(x, y, size, size, Component.literal("S"), 9, 35);
this.addRenderableWidget(sortButton);
}
// Show button
if (sortButton != null) {
sortButton.visible = true;
}
} else {
// Hide button when not on inventory tab
if (sortButton != null) {
sortButton.visible = false;
}
}
}

@Override
public boolean keyPressed(KeyEvent event) {
// Only allow sorting when on inventory tab
if (isInventoryTab() && LightweightInventorySortingClient.sortKeyBind.matches(event)) {
if (sortButton != null) {
Sorter.sortContainerClientside(Minecraft.getInstance(), sortButton.getSortStartIndex(), sortButton.getSortEndIndex());
}
return true;
}
return super.keyPressed(event);
}

@Override
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
// Only allow sorting when on inventory tab
if (isInventoryTab() && LightweightInventorySortingClient.sortKeyBind.matchesMouse(event)) {
if (sortButton != null) {
Sorter.sortContainerClientside(Minecraft.getInstance(), sortButton.getSortStartIndex(), sortButton.getSortEndIndex());
}
return true;
}
return super.mouseClicked(event, doubleClick);
}

@Unique
private boolean isInventoryTab() {
try {
// Access the static selectedTab field via reflection
var field = CreativeModeInventoryScreen.class.getDeclaredField("selectedTab");
field.setAccessible(true);
CreativeModeTab selectedTab = (CreativeModeTab) field.get(null);
return selectedTab != null && selectedTab.getType() == CreativeModeTab.Type.INVENTORY;
} catch (Exception e) {
// Default to false if we can't access the field
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package borknbeans.lightweightinventorysorting.mixin.client;

import borknbeans.lightweightinventorysorting.LightweightInventorySortingClient;
import borknbeans.lightweightinventorysorting.config.Config;
import borknbeans.lightweightinventorysorting.sorting.SortButton;
import borknbeans.lightweightinventorysorting.sorting.Sorter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.gui.screens.inventory.HorseInventoryScreen;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.HorseInventoryMenu;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;

@Mixin(HorseInventoryScreen.class)
public abstract class HorseInventoryScreenMixin extends AbstractContainerScreen<HorseInventoryMenu> {

@Unique
private SortButton sortButton;

public HorseInventoryScreenMixin(HorseInventoryMenu menu, Inventory inventory, Component title) {
super(menu, inventory, title);
}

@Override
protected void init() {
super.init();

int x = this.leftPos + this.imageWidth - 20 + Config.xOffsetContainer;
int y = this.topPos + 4 + Config.yOffsetContainer;
int size = Config.buttonSize.getButtonSize();
sortButton = new SortButton(x, y, size, size, Component.literal("S"), 0, getMenu().slots.size() - 37);
this.addRenderableWidget(sortButton);
}


@Override
public boolean keyPressed(KeyEvent event) {
if (LightweightInventorySortingClient.sortKeyBind.matches(event)) {
if (sortButton != null) {
Sorter.sortContainerClientside(Minecraft.getInstance(), sortButton.getSortStartIndex(), sortButton.getSortEndIndex());
}
return true;
}
return super.keyPressed(event);
}

@Override
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
if (LightweightInventorySortingClient.sortKeyBind.matchesMouse(event)) {
if (sortButton != null) {
Sorter.sortContainerClientside(Minecraft.getInstance(), sortButton.getSortStartIndex(), sortButton.getSortEndIndex());
}
return true;
}
return super.mouseClicked(event, doubleClick);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import net.minecraft.client.Minecraft;
import net.minecraft.world.inventory.ContainerInput;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;

import java.util.List;

Expand All @@ -17,6 +19,15 @@ public class ClickOperation {

private final List<Integer> delays = List.of(0, 5, 15); // in milliseconds

// Custom exception to signal that a click was intentionally skipped due to unsafe conditions
public static class SkippedUnsafeClickException extends Exception {
public final int slot;
public SkippedUnsafeClickException(int slot) {
super("Skipped unsafe click on slot " + slot);
this.slot = slot;
}
}

public ClickOperation(Minecraft client, int syncId, int targetSlot, ItemStack expectedStartingTargetStack, ItemStack expectedEndingTargetStack, ItemStack expectedStartingMouseStack, ItemStack expectedEndingMouseStack) {
this.client = client;
this.syncId = syncId;
Expand All @@ -42,6 +53,11 @@ public void execute() throws Exception {
throw new Exception("[Target: " + targetSlot + "] Starting target stack is not what we expected: (ACTUAL)" + getItemStackString(startingTargetStack) + " != (EXPECTED)" + getItemStackString(expectedStartingTargetStack));
}

// Safety guard: Check if this click is safe
if (!canSafelyClick(client, startingMouseStack)) {
throw new SkippedUnsafeClickException(targetSlot);
}

click();

Exception error = null;
Expand Down Expand Up @@ -88,4 +104,27 @@ private void postClickVerification() throws Exception {
private String getItemStackString(ItemStack stack) {
return String.format("%dx %s", stack.getCount(), stack.getItem().getName(stack).getString());
}

// Safety check: Verify slot is valid and can accept the item in hand
private boolean canSafelyClick(Minecraft client, ItemStack mouseStack) {
if (client.player == null) return false;

var handler = client.player.containerMenu;
if (targetSlot < 0 || targetSlot >= handler.slots.size()) return false;

var slot = handler.getSlot(targetSlot);
if (slot == null || !slot.isActive()) return false;

// Equipment / non-insertable slots will often reject generic items
// Check both directions: whether we can take items FROM this slot OR insert INTO it
if (!slot.mayPickup(client.player) && !slot.hasItem()) return false;

// If holding something, make sure this slot can accept it
if (!mouseStack.isEmpty() && !slot.mayPlace(mouseStack)) return false;

// If this slot has special restrictions (e.g. saddle/armor), skip it
if (slot.getMaxStackSize() <= 0) return false;

return true;
}
}
Loading
Loading