diff --git a/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java b/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java index 3c66e9c53..27ae4c0a9 100644 --- a/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java +++ b/src/main/java/com/lambda/mixin/render/HandledScreenMixin.java @@ -17,16 +17,29 @@ package com.lambda.mixin.render; +import com.lambda.event.EventFlow; +import com.lambda.event.events.InventoryEvent; import com.lambda.module.modules.render.ContainerPreview; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.client.gui.Click; import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.screen.slot.SlotActionType; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(HandledScreen.class) -public class HandledScreenMixin { +public class HandledScreenMixin { + @Final + @Shadow + protected T handler; + @Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true) private void onMouseClicked(Click click, boolean doubled, CallbackInfoReturnable cir) { if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { @@ -36,6 +49,14 @@ private void onMouseClicked(Click click, boolean doubled, CallbackInfoReturnable } } + @WrapMethod(method = "onMouseClick(Lnet/minecraft/screen/slot/Slot;IILnet/minecraft/screen/slot/SlotActionType;)V") + private void onMouseClickSlotPre(Slot slot, int slotId, int button, SlotActionType actionType, Operation original) { + if (!EventFlow.post(new InventoryEvent.SlotAction.Click(handler, slot, slotId, button, actionType)).isCanceled()) { + int slotIdFinal = slot == null ? slotId : slot.id; + original.call(slot, slotIdFinal, button, actionType); + } + } + @Inject(method = "mouseReleased", at = @At("HEAD"), cancellable = true) private void onMouseReleased(Click click, CallbackInfoReturnable cir) { if (ContainerPreview.INSTANCE.isEnabled() && ContainerPreview.isLocked()) { diff --git a/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt b/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt index 10d1e53b6..0d4419602 100644 --- a/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/InventoryEvent.kt @@ -22,6 +22,8 @@ import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable import net.minecraft.item.ItemStack import net.minecraft.screen.ScreenHandler +import net.minecraft.screen.slot.Slot +import net.minecraft.screen.slot.SlotActionType /** * Represents various events related to inventory interactions, updates, and state changes. @@ -82,6 +84,33 @@ sealed class InventoryEvent { val stack: ItemStack, ) : Event + /** + * Represents an action performed on an inventory slot, such as clicking or interacting with it. + */ + abstract class SlotAction : Event, ICancellable { + /** + * Represents a click action performed on an inventory slot. + * + * This event provides detailed information about the click action, including the screen handler, + * the slot involved, the button used, and the type of action performed. + * + * This event is [Cancellable], allowing handlers to prevent the default click behavior if necessary. + * + * @property screenHandler The screen handler managing the inventory interaction. + * @property slot The slot that was clicked, or null if no slot was involved. + * @property slotId The ID of the slot that was clicked. + * @property button The mouse button used for the click action. + * @property actionType The type of action performed on the slot. + */ + data class Click( + val screenHandler: ScreenHandler, + val slot: Slot?, + val slotId: Int, + val button: Int, + val actionType: SlotActionType, + ) : ICancellable by Cancellable() + } + abstract class HotbarSlot : Event { /** * Represents an event triggered when the client attempts to send slot update to the server. diff --git a/src/main/kotlin/com/lambda/module/modules/player/EasyTrash.kt b/src/main/kotlin/com/lambda/module/modules/player/EasyTrash.kt new file mode 100644 index 000000000..c30c592be --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/player/EasyTrash.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.player + +import com.lambda.context.SafeContext +import com.lambda.event.events.InventoryEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.managers.inventory.InventoryRequest.Companion.inventoryRequest +import com.lambda.interaction.material.StackSelection +import com.lambda.interaction.material.container.containers.InventoryContainer +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import net.minecraft.entity.EntityType +import net.minecraft.entity.ItemEntity +import net.minecraft.item.Item +import net.minecraft.item.Items +import net.minecraft.registry.Registries.ITEM +import net.minecraft.screen.GenericContainerScreenHandler +import net.minecraft.screen.ScreenHandler +import net.minecraft.screen.slot.SlotActionType + +object EasyTrash : Module( + name = "EasyTrash", + description = "Automatically trashes unwanted items", + tag = ModuleTag.PLAYER +) { + private val itemsCanTrash by setting("Items Can Trash", setOf(Items.NETHERRACK, Items.COBBLESTONE), ITEM.toSet()) + + private val dropToPickup by setting("Drop To Pickup", true) + private val itemsToPickup by setting("Items To Pickup", setOf(), ITEM.toSet()) + + init { + listen { + checkShouldTrashSomething() + } + + listen { event -> + if (event.actionType != SlotActionType.QUICK_MOVE || event.button != 0) { + return@listen + } + + if (event.screenHandler is GenericContainerScreenHandler) { + val sh = event.screenHandler + val rows = sh.rows + + if (event.slotId >= rows * 9) { + // Clicked in inventory, not in container + return@listen + } + + if (sh.slots.subList(rows * 9, sh.slots.size).any { !it.hasStack() }) { + // There is empty space in the inventory, no need to trash + return@listen + } + + StackSelection.selectStack { + isOneOfItems(itemsCanTrash) + }.filterSlots(InventoryContainer.slots).firstOrNull()?.let { trashSlot -> + inventoryRequest { + pickup(event.slotId, 0) + pickup(trashSlot.id, 0) + pickup(ScreenHandler.EMPTY_SPACE_SLOT_INDEX, 0) + }.submit(true) + event.cancel() + } + } + } + } + + fun SafeContext.checkShouldTrashSomething() { + if (player.health > 0.0f && !player.isSpectator && dropToPickup && !player.isCreative) { + val vehicle = player.vehicle + val box = if (vehicle != null && !vehicle.isRemoved) { + player.boundingBox.union(vehicle.boundingBox).expand(1.0, 0.0, 1.0) + } else { + player.boundingBox.expand(1.0, 0.5, 1.0) + } + val items = world.getEntitiesByType(EntityType.ITEM, box) { + entity -> itemsToPickup.contains(entity.stack.item) && entity.isOnGround + }.map { i -> i.stack.item } + if (items.isNotEmpty() && InventoryContainer.spaceAvailable(StackSelection.selectStack { isOneOfItems(items) }) <= 0) { + dropOneTrashStack() + } + } + } + + fun SafeContext.dropOneTrashStack(): Boolean { + StackSelection.selectStack { + isOneOfItems(itemsCanTrash) + }.filterSlots(InventoryContainer.slots).firstOrNull()?.let { + inventoryRequest { + throwStack(it.id) + }.submit(true) + return true + } + return false + } +} \ No newline at end of file