Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Quick trade module #5236

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -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();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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.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<MerchantScreenHandler> {
@Shadow
private int selectedIndex;

public MerchantScreenMixin(MerchantScreenHandler handler, PlayerInventory inventory, Text title) {
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")
)
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.trade(selectedOffer, handler, this.selectedIndex);
}
}

Original file line number Diff line number Diff line change
@@ -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());
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* 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.BoolSetting;
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 java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Supplier;

public class QuickTrade extends Module {
private final List<Supplier<Boolean>> tasks = Collections.synchronizedList(new LinkedList<>());
private final SettingGroup sgGeneral = settings.getDefaultGroup();

public final Setting<Keybind> modifier = sgGeneral.add(new KeybindSetting.Builder()
.name("Activation key")
.description("Key to press to perform trade until exhausted.")
.defaultValue(Keybind.none())
.build()
);

public final Setting<Boolean> 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.");
}

@EventHandler
private void onTick(TickEvent.Pre event) {
synchronized (tasks) {
tasks.removeIf(task -> !task.get());
}
}

private void runUntilFalse(Supplier<Boolean> task) {
tasks.add(task);
}

public void trade(TradeOffer selectedOffer, MerchantScreenHandler handler, int selectedIndex) {
final MinecraftClient client = MinecraftClient.getInstance();

Slot inSlot0 = handler.getSlot(0);
Slot inSlot1 = handler.getSlot(1);
Slot outputSlot = handler.getSlot(2);

runUntilFalse(() -> {

if (client.interactionManager == null) {
error("Client interaction manager is null!");
return false;
}

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);

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);

if (drop.get()) {
client.interactionManager.clickSlot(handler.syncId, inSlot1.id, 1, SlotActionType.THROW, client.player);
} else if (!inSlot1.getStack().isEmpty()) {
return false;
}
}

// 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 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;
});
}

public boolean hasSpace(PlayerInventory inv, ItemStack outStack) {
return outStack.isEmpty() || inv.getEmptySlot() >= 0 || inv.getOccupiedSlotWithRoomForStack(outStack) >= 0;
}
}
2 changes: 2 additions & 0 deletions src/main/resources/meteor-client.mixins.json
Original file line number Diff line number Diff line change
@@ -126,6 +126,8 @@
"LivingEntityRendererMixin",
"MapRendererMixin",
"MapTextureManagerAccessor",
"MerchantScreenMixin",
"MerchantScreenHandlerAccessor",
"MessageHandlerMixin",
"MinecraftClientAccessor",
"MinecraftClientMixin",