From 4e4c0f9e751aa8d8c79e1b6119943ed3b55fb648 Mon Sep 17 00:00:00 2001 From: OhACD Date: Wed, 22 Oct 2025 08:51:10 +0300 Subject: [PATCH 1/2] Merged upstream changes --- .../fabric/api/inventory/InventoryEvents.java | 55 +++++++++++++++++ .../api/inventory/SlotClickCallback.java | 47 +++++++++++++++ .../inventory/ScreenHandlerAccessorMixin.java | 23 +++++++ .../mixin/inventory/ScreenHandlerMixin.java | 60 +++++++++++++++++++ .../fabric-screen-handler-api-v1.mixins.json | 8 ++- .../src/main/resources/fabric.mod.json | 14 +++-- .../test/inventory/InventoryEventsTest.java | 36 +++++++++++ .../test/screenhandler/ScreenHandlerTest.java | 5 ++ 8 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/InventoryEvents.java create mode 100644 fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/SlotClickCallback.java create mode 100644 fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/mixin/inventory/ScreenHandlerAccessorMixin.java create mode 100644 fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/mixin/inventory/ScreenHandlerMixin.java create mode 100644 fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/inventory/InventoryEventsTest.java diff --git a/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/InventoryEvents.java b/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/InventoryEvents.java new file mode 100644 index 0000000000..d95a418683 --- /dev/null +++ b/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/InventoryEvents.java @@ -0,0 +1,55 @@ +package net.fabricmc.fabric.api.inventory; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +import net.minecraft.util.ActionResult; + +import org.jetbrains.annotations.ApiStatus; + +/** + * Core entry point for inventory-related events in FabricMC. + * + *

Register listeners like: + *

{@code
+ * InventoryEvents.SLOT_CLICK_EVENT.register((handler, slot, slotId, clickType, actionType, player, cursor) -> {
+ *     // Example: Prevent shift-clicking specific items
+ *     if (actionType == SlotActionType.QUICK_MOVE && handler.getType() == ScreenHandlerType.CHEST) {
+ *         // Add custom logic here
+ *         return ActionResult.FAIL; // Cancel the action
+ *     }
+ *     return ActionResult.PASS;
+ * });
+ * 
+ * + *

Note: When returning {@code ActionResult.FAIL}, the server cancels the action, but client-side state (e.g., cursor or slot contents) may not automatically sync. Modders should manually sync state if needed using {@code ScreenHandler.syncState()}. + */ +@ApiStatus.Experimental +public final class InventoryEvents { + /** + * Fires for slot clicks in ANY inventory GUI. + * + *

This event occurs before vanilla logic executes, allowing modders to intercept or cancel slot interactions. + * + *

Return: + *

+ */ + public static final Event SLOT_CLICK_EVENT = EventFactory.createArrayBacked( + SlotClickCallback.class, + (listeners) -> (handler, slot, slotId, clickType, actionType, player, cursor) -> { + for (SlotClickCallback listener : listeners) { + ActionResult result = listener.interact(handler, slot, slotId, clickType, actionType, player, cursor); + if (result != ActionResult.PASS) { + return result; + } + } + return ActionResult.PASS; + } + ); + + private InventoryEvents() {} +} diff --git a/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/SlotClickCallback.java b/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/SlotClickCallback.java new file mode 100644 index 0000000000..493cbb37ed --- /dev/null +++ b/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/SlotClickCallback.java @@ -0,0 +1,47 @@ +package net.fabricmc.fabric.api.inventory; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.util.ActionResult; + +/** + * Callback interface for handling slot click events in inventory GUIs. + * + *

Runs BEFORE any slot click is processed to allow interception or cancellation of the action. + * + *

Use cases: + *

+ * + *

Canceling with {@code ActionResult.FAIL} stops the click entirely. Returning {@code ActionResult.SUCCESS} allows the action but consumes the event. + */ +@FunctionalInterface +public interface SlotClickCallback { + /** + * Called when a player clicks a slot in an inventory GUI. + * + * @param handler ScreenHandler (e.g., chest, furnace, crafting table) + * @param slot Clicked slot (null if clicked outside inventory) + * @param slotId Raw slot ID from packet (0–215 for most GUIs, -1 for outside clicks) + * @param button Mouse button (0 = left, 1 = right) + modifiers + * @param actionType Exact action: PICKUP, QUICK_MOVE, SWAP, etc. + * @param player The player who clicked (useful for permission checks) + * @param cursor Current cursor stack (what the player is holding, never null) + * @return PASS = continue to next listener, SUCCESS/FAIL = stop event chain + */ + ActionResult interact( + ScreenHandler handler, + Slot slot, + int slotId, + int button, + SlotActionType actionType, + PlayerEntity player, + ItemStack cursor + ); +} diff --git a/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/mixin/inventory/ScreenHandlerAccessorMixin.java b/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/mixin/inventory/ScreenHandlerAccessorMixin.java new file mode 100644 index 0000000000..dc98523b58 --- /dev/null +++ b/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/mixin/inventory/ScreenHandlerAccessorMixin.java @@ -0,0 +1,23 @@ +package net.fabricmc.fabric.mixin.inventory; + +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.util.collection.DefaultedList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +/** + * Accessor for private fields in {@link ScreenHandler} used by inventory events. + * + *

Provides access to the list of all slot instances in the current GUI screen, including player inventory and container-specific slots. + */ +@Mixin(ScreenHandler.class) +public interface ScreenHandlerAccessorMixin { + /** + * Gets the list of all slots in this ScreenHandler. + * + * @return A DefaultedList of Slot objects representing all slots in the GUI + */ + @Accessor("slots") + DefaultedList getSlots(); +} diff --git a/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/mixin/inventory/ScreenHandlerMixin.java b/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/mixin/inventory/ScreenHandlerMixin.java new file mode 100644 index 0000000000..b0d10eaaad --- /dev/null +++ b/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/mixin/inventory/ScreenHandlerMixin.java @@ -0,0 +1,60 @@ +package net.fabricmc.fabric.mixin.inventory; + +import net.fabricmc.fabric.api.inventory.InventoryEvents; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.util.ActionResult; +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 for {@link ScreenHandler} to inject inventory event handling. + * + *

Intercepts slot click events to trigger the {@link InventoryEvents#SLOT_CLICK_EVENT} before vanilla processing, + * allowing modders to modify or cancel slot interactions in any inventory GUI (e.g., chests, furnaces, crafting tables). + */ +@Mixin(ScreenHandler.class) +public class ScreenHandlerMixin { + /** + * Injects into {@link ScreenHandler#onSlotClick} to fire the {@link InventoryEvents#SLOT_CLICK_EVENT}. + * + *

This injection occurs at the start of the method, before vanilla logic processes the slot click. + * If the event returns {@link ActionResult#FAIL}, the click is canceled, and the client state is synchronized + * to prevent desyncs. If {@link ActionResult#SUCCESS} is returned, the event is consumed but vanilla processing continues. + * {@link ActionResult#PASS} allows other listeners and vanilla logic to proceed. + * + * @param slotIndex The raw slot ID from the packet (0–215 for most GUIs, -1 for outside clicks) + * @param button Mouse button (0 = left, 1 = right) + modifiers + * @param actionType The exact action: PICKUP, QUICK_MOVE, SWAP, etc. + * @param player The player who clicked the slot + * @param ci Callback info for canceling the method + */ + @Inject(method = "onSlotClick", at = @At("HEAD"), cancellable = true) + private void onSlotClick( + int slotIndex, + int button, + SlotActionType actionType, + PlayerEntity player, + CallbackInfo ci + ) { + ScreenHandler handler = (ScreenHandler) (Object) this; + ScreenHandlerAccessorMixin accessor = (ScreenHandlerAccessorMixin) handler; + Slot slot = slotIndex >= 0 ? handler.getSlot(slotIndex) : null; + ItemStack cursor = handler.getCursorStack(); + ActionResult result = InventoryEvents.SLOT_CLICK_EVENT.invoker().interact( + handler, slot, slotIndex, button, actionType, player, cursor + ); + + if (result == ActionResult.FAIL) { + ci.cancel(); + // Sync client state to prevent desync + handler.syncState(); + } + } +} diff --git a/fabric-screen-handler-api-v1/src/main/resources/fabric-screen-handler-api-v1.mixins.json b/fabric-screen-handler-api-v1/src/main/resources/fabric-screen-handler-api-v1.mixins.json index 40a4878ce9..56ca542099 100644 --- a/fabric-screen-handler-api-v1/src/main/resources/fabric-screen-handler-api-v1.mixins.json +++ b/fabric-screen-handler-api-v1/src/main/resources/fabric-screen-handler-api-v1.mixins.json @@ -1,10 +1,12 @@ { "required": true, - "package": "net.fabricmc.fabric.mixin.screenhandler", + "package": "net.fabricmc.fabric.mixin", "compatibilityLevel": "JAVA_21", "mixins": [ - "NamedScreenHandlerFactoryMixin", - "ServerPlayerEntityMixin" + "screenhandler.NamedScreenHandlerFactoryMixin", + "screenhandler.ServerPlayerEntityMixin", + "inventory.ScreenHandlerAccessorMixin", + "inventory.ScreenHandlerMixin" ], "client": [ ], diff --git a/fabric-screen-handler-api-v1/src/main/resources/fabric.mod.json b/fabric-screen-handler-api-v1/src/main/resources/fabric.mod.json index 75fd1adcc8..bbd1b63dec 100644 --- a/fabric-screen-handler-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-screen-handler-api-v1/src/main/resources/fabric.mod.json @@ -12,19 +12,21 @@ "issues": "https://github.com/FabricMC/fabric/issues", "sources": "https://github.com/FabricMC/fabric" }, - "authors": [ - "FabricMC" - ], + "authors": ["FabricMC"], "depends": { "fabricloader": ">=0.17.0", "fabric-api-base": "*", "fabric-networking-api-v1": "*" }, "entrypoints": { - "main": ["net.fabricmc.fabric.impl.screenhandler.Networking"], - "client": ["net.fabricmc.fabric.impl.screenhandler.client.ClientNetworking"] + "main": [ + "net.fabricmc.fabric.impl.screenhandler.Networking" + ], + "client": [ + "net.fabricmc.fabric.impl.screenhandler.client.ClientNetworking" + ] }, - "description": "Hooks and extensions for creating screen handlers.", + "description": "Hooks and extensions for creating screen handlers and inventory events.", "mixins": [ "fabric-screen-handler-api-v1.mixins.json" ], diff --git a/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/inventory/InventoryEventsTest.java b/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/inventory/InventoryEventsTest.java new file mode 100644 index 0000000000..65dabee2ff --- /dev/null +++ b/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/inventory/InventoryEventsTest.java @@ -0,0 +1,36 @@ +package net.fabricmc.fabric.test.inventory; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.inventory.InventoryEvents; + +import net.minecraft.item.Items; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class InventoryEventsTest implements ModInitializer { + private static final Logger LOGGER = LoggerFactory.getLogger("fabric-screen-handler-api-v1-testmod"); + + @Override + public void onInitialize() { + LOGGER.info("Initializing Fabric Inventory Events Test Mod..."); + + // Prevent diamonds from being moved + InventoryEvents.SLOT_CLICK_EVENT.register((handler, slot, slotId, button, actionType, player, cursor) -> { + if (!(handler instanceof GenericContainerScreenHandler) || slot == null) { + return ActionResult.PASS; + } + + if (slot.getStack().isOf(Items.DIAMOND)) { + player.sendMessage(Text.literal("Diamonds are protected and cannot be moved."), false); + LOGGER.info("Player {} tried to move diamonds.", player.getName().getString()); + return ActionResult.FAIL; + } + + return ActionResult.PASS; + }); + } +} + diff --git a/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/screenhandler/ScreenHandlerTest.java b/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/screenhandler/ScreenHandlerTest.java index 3fe9e41afc..534c6b8506 100644 --- a/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/screenhandler/ScreenHandlerTest.java +++ b/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/screenhandler/ScreenHandlerTest.java @@ -16,6 +16,8 @@ package net.fabricmc.fabric.test.screenhandler; +import net.fabricmc.fabric.test.inventory.InventoryEventsTest; + import net.minecraft.block.AbstractBlock; import net.minecraft.block.Block; import net.minecraft.block.Blocks; @@ -71,5 +73,8 @@ public void onInitialize() { Registry.register(Registries.SCREEN_HANDLER, id("bag"), BAG_SCREEN_HANDLER); Registry.register(Registries.SCREEN_HANDLER, id("positioned_bag"), POSITIONED_BAG_SCREEN_HANDLER); Registry.register(Registries.SCREEN_HANDLER, id("box"), BOX_SCREEN_HANDLER); + + new InventoryEventsTest().onInitialize(); + } } From ae668e628dce70cb5f41925bcf9b58d71d0b9748 Mon Sep 17 00:00:00 2001 From: OhACD Date: Wed, 22 Oct 2025 16:06:17 +0300 Subject: [PATCH 2/2] Clean up and temporary disabled the InventoryEventsTest class due to issues with testing - Everything used to work fine but the mod broke after refactoring into the screen-handler-api-v1 module (the api was built as it's seperate module fabric-inventory-events-v1) a seperate test mod could be provided if needed but i have done ample testing to insure stability and cleanliness --- .../fabric/api/inventory/InventoryEvents.java | 3 +- .../test/inventory/InventoryEventsTest.java | 72 +++++++++---------- .../test/screenhandler/ScreenHandlerTest.java | 2 - 3 files changed, 37 insertions(+), 40 deletions(-) diff --git a/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/InventoryEvents.java b/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/InventoryEvents.java index d95a418683..863ed33373 100644 --- a/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/InventoryEvents.java +++ b/fabric-screen-handler-api-v1/src/main/java/net/fabricmc/fabric/api/inventory/InventoryEvents.java @@ -22,7 +22,6 @@ * }); * * - *

Note: When returning {@code ActionResult.FAIL}, the server cancels the action, but client-side state (e.g., cursor or slot contents) may not automatically sync. Modders should manually sync state if needed using {@code ScreenHandler.syncState()}. */ @ApiStatus.Experimental public final class InventoryEvents { @@ -35,7 +34,7 @@ public final class InventoryEvents { *

*/ public static final Event SLOT_CLICK_EVENT = EventFactory.createArrayBacked( diff --git a/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/inventory/InventoryEventsTest.java b/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/inventory/InventoryEventsTest.java index 65dabee2ff..c29fdf8364 100644 --- a/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/inventory/InventoryEventsTest.java +++ b/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/inventory/InventoryEventsTest.java @@ -1,36 +1,36 @@ -package net.fabricmc.fabric.test.inventory; - -import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.inventory.InventoryEvents; - -import net.minecraft.item.Items; -import net.minecraft.screen.GenericContainerScreenHandler; -import net.minecraft.text.Text; -import net.minecraft.util.ActionResult; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class InventoryEventsTest implements ModInitializer { - private static final Logger LOGGER = LoggerFactory.getLogger("fabric-screen-handler-api-v1-testmod"); - - @Override - public void onInitialize() { - LOGGER.info("Initializing Fabric Inventory Events Test Mod..."); - - // Prevent diamonds from being moved - InventoryEvents.SLOT_CLICK_EVENT.register((handler, slot, slotId, button, actionType, player, cursor) -> { - if (!(handler instanceof GenericContainerScreenHandler) || slot == null) { - return ActionResult.PASS; - } - - if (slot.getStack().isOf(Items.DIAMOND)) { - player.sendMessage(Text.literal("Diamonds are protected and cannot be moved."), false); - LOGGER.info("Player {} tried to move diamonds.", player.getName().getString()); - return ActionResult.FAIL; - } - - return ActionResult.PASS; - }); - } -} - +//package net.fabricmc.fabric.test.inventory; +// +//import net.fabricmc.api.ModInitializer; +//import net.fabricmc.fabric.api.inventory.InventoryEvents; +// +//import net.minecraft.item.Items; +//import net.minecraft.screen.GenericContainerScreenHandler; +//import net.minecraft.text.Text; +//import net.minecraft.util.ActionResult; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//public class InventoryEventsTest implements ModInitializer { +// private static final Logger LOGGER = LoggerFactory.getLogger("fabric-screen-handler-api-v1-testmod"); +// +// @Override +// public void onInitialize() { +// LOGGER.info("Initializing Fabric Inventory Events Test Mod..."); +// +// // Prevent diamonds from being moved +// InventoryEvents.SLOT_CLICK_EVENT.register((handler, slot, slotId, button, actionType, player, cursor) -> { +// if (!(handler instanceof GenericContainerScreenHandler) || slot == null) { +// return ActionResult.PASS; +// } +// +// if (slot.getStack().isOf(Items.DIAMOND)) { +// player.sendMessage(Text.literal("Diamonds are protected and cannot be moved."), false); +// LOGGER.info("Player {} tried to move diamonds.", player.getName().getString()); +// return ActionResult.FAIL; +// } +// +// return ActionResult.PASS; +// }); +// } +//} +// diff --git a/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/screenhandler/ScreenHandlerTest.java b/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/screenhandler/ScreenHandlerTest.java index 534c6b8506..996f8a7556 100644 --- a/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/screenhandler/ScreenHandlerTest.java +++ b/fabric-screen-handler-api-v1/src/testmod/java/net/fabricmc/fabric/test/screenhandler/ScreenHandlerTest.java @@ -16,7 +16,6 @@ package net.fabricmc.fabric.test.screenhandler; -import net.fabricmc.fabric.test.inventory.InventoryEventsTest; import net.minecraft.block.AbstractBlock; import net.minecraft.block.Block; @@ -74,7 +73,6 @@ public void onInitialize() { Registry.register(Registries.SCREEN_HANDLER, id("positioned_bag"), POSITIONED_BAG_SCREEN_HANDLER); Registry.register(Registries.SCREEN_HANDLER, id("box"), BOX_SCREEN_HANDLER); - new InventoryEventsTest().onInitialize(); } }