From d6b2e07fd94d21755af76c3f1354be6f83959c93 Mon Sep 17 00:00:00 2001 From: MattMX Date: Sat, 8 Mar 2025 15:31:47 +0000 Subject: [PATCH 1/5] :fire: Working quick-trade module --- .../mixin/MerchantScreenHandlerAccessor.java | 19 +++ .../mixin/MerchantScreenMixin.java | 60 ++++++++ .../meteorclient/systems/modules/Modules.java | 1 + .../systems/modules/world/QuickTrade.java | 128 ++++++++++++++++++ src/main/resources/meteor-client.mixins.json | 2 + 5 files changed, 210 insertions(+) create mode 100644 src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenHandlerAccessor.java create mode 100644 src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java create mode 100644 src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenHandlerAccessor.java b/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenHandlerAccessor.java new file mode 100644 index 0000000000..61c14c966a --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenHandlerAccessor.java @@ -0,0 +1,19 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.mixin; + +import net.minecraft.screen.MerchantScreenHandler; +import net.minecraft.village.Merchant; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(MerchantScreenHandler.class) +public interface MerchantScreenHandlerAccessor { + + @Accessor + Merchant getMerchant(); + +} diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java b/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java new file mode 100644 index 0000000000..fa562205bf --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java @@ -0,0 +1,60 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.mixin; + +import meteordevelopment.meteorclient.systems.modules.Modules; +import meteordevelopment.meteorclient.systems.modules.world.QuickTrade; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.client.gui.screen.ingame.MerchantScreen; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.screen.MerchantScreenHandler; +import net.minecraft.text.Text; +import net.minecraft.village.TradeOffer; +import net.minecraft.village.TradeOfferList; +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.CallbackInfo; + +@Mixin(MerchantScreen.class) +public abstract class MerchantScreenMixin extends HandledScreen { + @Shadow + private int selectedIndex; + + public MerchantScreenMixin(MerchantScreenHandler handler, PlayerInventory inventory, Text title) { + super(handler, inventory, title); + } + + @Inject( + method = "syncRecipeIndex", + at = @At("TAIL") + ) + private void syncRecipeIndex(CallbackInfo ci) { + // Called when a new trade is selected, server is notified at end of call + + QuickTrade module = Modules.get().get(QuickTrade.class); + + if (!module.isActive()) { + return; + } + + if (!module.modifier.get().isPressed()) { + return; + } + + + TradeOfferList tradeOfferList = this.handler.getRecipes(); + if (tradeOfferList.size() < selectedIndex) return; + + TradeOffer selectedOffer = tradeOfferList.get(selectedIndex); + MerchantScreenHandler handler = getScreenHandler(); + module.tradeUntilDoneOrEmpty(selectedOffer, handler, this.selectedIndex); + } +} + diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/Modules.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/Modules.java index 8fadc77779..d0edba7036 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/Modules.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/Modules.java @@ -565,6 +565,7 @@ private void initWorld() { add(new SpawnProofer()); add(new Timer()); add(new VeinMiner()); + add(new QuickTrade()); if (BaritoneUtils.IS_AVAILABLE) { add(new Excavator()); diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java new file mode 100644 index 0000000000..1f7afbdf99 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java @@ -0,0 +1,128 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.systems.modules.world; + +import meteordevelopment.meteorclient.events.world.TickEvent; +import meteordevelopment.meteorclient.settings.KeybindSetting; +import meteordevelopment.meteorclient.settings.Setting; +import meteordevelopment.meteorclient.settings.SettingGroup; +import meteordevelopment.meteorclient.systems.modules.Categories; +import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.utils.misc.Keybind; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.network.packet.c2s.play.SelectMerchantTradeC2SPacket; +import net.minecraft.screen.MerchantScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.village.TradeOffer; +import org.lwjgl.glfw.GLFW; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Supplier; + +public class QuickTrade extends Module { + private final List> tasks = Collections.synchronizedList(new LinkedList<>()); + private final SettingGroup sgGeneral = settings.getDefaultGroup(); + + public final Setting modifier = sgGeneral.add(new KeybindSetting.Builder() + .name("Activation key") + .description("Key to press to perform trade until exhausted.") + .defaultValue(Keybind.fromButton(GLFW.GLFW_KEY_LEFT_CONTROL)) + .build() + ); + + public QuickTrade() { + super(Categories.World, "quick-trade", "Quickly perform trades with villagers."); + } + + @EventHandler + private void onTick(TickEvent.Pre event) { + synchronized (tasks) { + tasks.removeIf(task -> !task.get()); + } + } + + private void runUntilFalse(Supplier task) { + tasks.add(task); + } + + public void tradeUntilDoneOrEmpty(TradeOffer selectedOffer, MerchantScreenHandler handler, int selectedIndex) { + final MinecraftClient client = MinecraftClient.getInstance(); + + if (client.interactionManager == null) { + error("Client interaction manager is null!"); + return; + } + + Slot inSlot0 = handler.getSlot(0); + Slot inSlot1 = handler.getSlot(1); + Slot outputSlot = handler.getSlot(2); + runUntilFalse(() -> { + // If player is null or they changed screen + if (client.player == null) { + error("Player is null, stopping trading."); + return false; + } + + if (client.player.currentScreenHandler != handler) { + error("Screen is closed, stopping trading."); + return false; + } + + if (client.getNetworkHandler() == null) { + error("Network handler is null, stopping trading."); + return false; + } + + // If there is an item in the trade slot(s) already, then shift click or drop them + if (!inSlot0.getStack().isEmpty()) { + client.interactionManager.clickSlot(handler.syncId, inSlot0.id, 1, SlotActionType.QUICK_MOVE, client.player); + client.interactionManager.clickSlot(handler.syncId, inSlot0.id, 1, SlotActionType.THROW, client.player); + } + + if (!inSlot1.getStack().isEmpty()) { + client.interactionManager.clickSlot(handler.syncId, inSlot1.id, 1, SlotActionType.QUICK_MOVE, client.player); + client.interactionManager.clickSlot(handler.syncId, inSlot1.id, 1, SlotActionType.THROW, client.player); + } + + // Refresh items + handler.setRecipeIndex(selectedIndex); + handler.switchTo(selectedIndex); + + client.getNetworkHandler().sendPacket(new SelectMerchantTradeC2SPacket(selectedIndex)); + + // Out of materials OR trade is out of stock + + // todo auto-convert emerald blocks if we're out of them + // todo auto-trade without clicking a trade (preset trades?) + boolean shouldStopTrading = !selectedOffer.matchesBuyItems( + handler.slots.get(0).getStack(), + handler.slots.get(1).getStack()) + || selectedOffer.isDisabled(); + + if (shouldStopTrading) { + return false; + } + + if (hasSpace(client.player.getInventory(), selectedOffer.getSellItem())) { + client.interactionManager.clickSlot(handler.syncId, outputSlot.id, 0, SlotActionType.QUICK_MOVE, client.player); + } else { + client.interactionManager.clickSlot(handler.syncId, outputSlot.id, 0, SlotActionType.THROW, client.player); + } + + return true; + }); + } + + public boolean hasSpace(PlayerInventory inv, ItemStack outStack) { + return outStack.isEmpty() || inv.getEmptySlot() >= 0 || inv.getOccupiedSlotWithRoomForStack(outStack) >= 0; + } +} diff --git a/src/main/resources/meteor-client.mixins.json b/src/main/resources/meteor-client.mixins.json index 05fd2e9657..efa4605987 100644 --- a/src/main/resources/meteor-client.mixins.json +++ b/src/main/resources/meteor-client.mixins.json @@ -126,6 +126,8 @@ "LivingEntityRendererMixin", "MapRendererMixin", "MapTextureManagerAccessor", + "MerchantScreenMixin", + "MerchantScreenHandlerAccessor", "MessageHandlerMixin", "MinecraftClientAccessor", "MinecraftClientMixin", From 97cbfd505b2d72f86d60e44bd6e9cc736fcd4caf Mon Sep 17 00:00:00 2001 From: MattMX Date: Sat, 8 Mar 2025 15:37:15 +0000 Subject: [PATCH 2/5] :fire: Fix default bind key --- .../meteorclient/systems/modules/world/QuickTrade.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java index 1f7afbdf99..760efb30ff 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java @@ -35,7 +35,7 @@ public class QuickTrade extends Module { public final Setting modifier = sgGeneral.add(new KeybindSetting.Builder() .name("Activation key") .description("Key to press to perform trade until exhausted.") - .defaultValue(Keybind.fromButton(GLFW.GLFW_KEY_LEFT_CONTROL)) + .defaultValue(Keybind.none()) .build() ); From 53bf23d26fa4881cf22418e1f1ac1bf25bd73352 Mon Sep 17 00:00:00 2001 From: MattMX Date: Sat, 8 Mar 2025 15:40:49 +0000 Subject: [PATCH 3/5] :fire: Text when holding key down --- .../mixin/MerchantScreenMixin.java | 25 ++++++++++++++++++- .../systems/modules/world/QuickTrade.java | 1 - 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java b/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java index fa562205bf..f01df58e03 100644 --- a/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java +++ b/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java @@ -8,7 +8,6 @@ import meteordevelopment.meteorclient.systems.modules.Modules; import meteordevelopment.meteorclient.systems.modules.world.QuickTrade; import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.client.gui.screen.ingame.MerchantScreen; import net.minecraft.entity.player.PlayerInventory; @@ -31,6 +30,30 @@ public MerchantScreenMixin(MerchantScreenHandler handler, PlayerInventory invent super(handler, inventory, title); } + @Inject( + method = "render", + at = @At("TAIL") + ) + private void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { + + if (client == null) { + return; + } + + QuickTrade module = Modules.get().get(QuickTrade.class); + + + if (!module.isActive()) { + return; + } + + if (!module.modifier.get().isPressed()) { + return; + } + + context.drawCenteredTextWithShadow(client.textRenderer, "Select a trade on the left to quick-trade", width / 2, height / 2 + 100, 0xFFFFFFFF); + } + @Inject( method = "syncRecipeIndex", at = @At("TAIL") diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java index 760efb30ff..6ab3cd1fb2 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java @@ -21,7 +21,6 @@ import net.minecraft.screen.slot.Slot; import net.minecraft.screen.slot.SlotActionType; import net.minecraft.village.TradeOffer; -import org.lwjgl.glfw.GLFW; import java.util.Collections; import java.util.LinkedList; From 15674a873946c55eaa7945274c5f1ec81e5aba06 Mon Sep 17 00:00:00 2001 From: MattMX Date: Sat, 8 Mar 2025 15:43:23 +0000 Subject: [PATCH 4/5] :fire: Added option to disable dropping --- .../systems/modules/world/QuickTrade.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java index 6ab3cd1fb2..e4901d95b9 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java @@ -6,6 +6,7 @@ package meteordevelopment.meteorclient.systems.modules.world; import meteordevelopment.meteorclient.events.world.TickEvent; +import meteordevelopment.meteorclient.settings.BoolSetting; import meteordevelopment.meteorclient.settings.KeybindSetting; import meteordevelopment.meteorclient.settings.Setting; import meteordevelopment.meteorclient.settings.SettingGroup; @@ -38,6 +39,13 @@ public class QuickTrade extends Module { .build() ); + public final Setting drop = sgGeneral.add(new BoolSetting.Builder() + .name("Drop if full") + .description("Should we drop items on the floor if we run out of inventory space?") + .defaultValue(true) + .build() + ); + public QuickTrade() { super(Categories.World, "quick-trade", "Quickly perform trades with villagers."); } @@ -84,12 +92,23 @@ public void tradeUntilDoneOrEmpty(TradeOffer selectedOffer, MerchantScreenHandle // If there is an item in the trade slot(s) already, then shift click or drop them if (!inSlot0.getStack().isEmpty()) { client.interactionManager.clickSlot(handler.syncId, inSlot0.id, 1, SlotActionType.QUICK_MOVE, client.player); - client.interactionManager.clickSlot(handler.syncId, inSlot0.id, 1, SlotActionType.THROW, client.player); + + if (drop.get()) { + client.interactionManager.clickSlot(handler.syncId, inSlot0.id, 1, SlotActionType.THROW, client.player); + } else if (!inSlot0.getStack().isEmpty()) { + // If still not empty then we should stop trading + return false; + } } if (!inSlot1.getStack().isEmpty()) { client.interactionManager.clickSlot(handler.syncId, inSlot1.id, 1, SlotActionType.QUICK_MOVE, client.player); - client.interactionManager.clickSlot(handler.syncId, inSlot1.id, 1, SlotActionType.THROW, client.player); + + if (drop.get()) { + client.interactionManager.clickSlot(handler.syncId, inSlot1.id, 1, SlotActionType.THROW, client.player); + } else if (!inSlot1.getStack().isEmpty()) { + return false; + } } // Refresh items @@ -113,8 +132,11 @@ public void tradeUntilDoneOrEmpty(TradeOffer selectedOffer, MerchantScreenHandle if (hasSpace(client.player.getInventory(), selectedOffer.getSellItem())) { client.interactionManager.clickSlot(handler.syncId, outputSlot.id, 0, SlotActionType.QUICK_MOVE, client.player); - } else { + } else if (drop.get()) { client.interactionManager.clickSlot(handler.syncId, outputSlot.id, 0, SlotActionType.THROW, client.player); + } else { + // Out of inventory space and drop is not enabled - stop trading + return false; } return true; From bbca66484dc5b8b14a3815480e2903ee58dc683a Mon Sep 17 00:00:00 2001 From: MattMX Date: Sat, 8 Mar 2025 17:14:24 +0000 Subject: [PATCH 5/5] :bug: Fix possible client crash + rename function --- .../meteorclient/mixin/MerchantScreenMixin.java | 3 +-- .../systems/modules/world/QuickTrade.java | 15 ++++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java b/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java index f01df58e03..96f8b00e75 100644 --- a/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java +++ b/src/main/java/meteordevelopment/meteorclient/mixin/MerchantScreenMixin.java @@ -71,13 +71,12 @@ private void syncRecipeIndex(CallbackInfo ci) { return; } - TradeOfferList tradeOfferList = this.handler.getRecipes(); if (tradeOfferList.size() < selectedIndex) return; TradeOffer selectedOffer = tradeOfferList.get(selectedIndex); MerchantScreenHandler handler = getScreenHandler(); - module.tradeUntilDoneOrEmpty(selectedOffer, handler, this.selectedIndex); + module.trade(selectedOffer, handler, this.selectedIndex); } } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java index e4901d95b9..e77a55b4d9 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/world/QuickTrade.java @@ -61,19 +61,20 @@ private void runUntilFalse(Supplier task) { tasks.add(task); } - public void tradeUntilDoneOrEmpty(TradeOffer selectedOffer, MerchantScreenHandler handler, int selectedIndex) { + public void trade(TradeOffer selectedOffer, MerchantScreenHandler handler, int selectedIndex) { final MinecraftClient client = MinecraftClient.getInstance(); - if (client.interactionManager == null) { - error("Client interaction manager is null!"); - return; - } - Slot inSlot0 = handler.getSlot(0); Slot inSlot1 = handler.getSlot(1); Slot outputSlot = handler.getSlot(2); + runUntilFalse(() -> { - // If player is null or they changed screen + + if (client.interactionManager == null) { + error("Client interaction manager is null!"); + return false; + } + if (client.player == null) { error("Player is null, stopping trading."); return false;