package autodig.client.service; import autodig.client.config.DigConfig; import autodig.client.config.MineTask; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.BlockItem; import net.minecraft.item.ItemStack; import net.minecraft.registry.Registries; import net.minecraft.screen.ScreenHandler; import net.minecraft.screen.slot.SlotActionType; /** * 背包垃圾自动清理服务。 * *

背包接近满(已用格数达到阈值)时就主动把 {@code trashItems} 清单里的 * 垃圾物品丢出背包(圆石、凝灰岩、深板岩等挖掘副产物),为挖到的矿物提前 * 腾出空间,避免背包彻底满后触发"背包已满需返回"而中断挖矿。方块类垃圾 * 保留 {@code trashReserve} 个供 Baritone 搭路/垫脚使用。丢弃通过 * {@code clickSlot(THROW)} 走正规网络包,单人/多人均可用。

*/ public final class AutoTrash { /** 每 tick 最多丢弃的物品组数,避免一次产生过多数据包 */ private static final int MAX_DROPS_PER_TICK = 4; /** 背包已用格数达到该值(共 36 格)即开始丢垃圾,留出余量防误判满包 */ private static final int NEARLY_FULL_SLOTS = 30; /** 每帧调用:背包接近满时提前丢弃垃圾物品。 */ public void tick(DigConfig config) { MinecraftClient client = MinecraftClient.getInstance(); if (client.player == null || client.world == null || client.interactionManager == null) { return; } if (!config.autoTrash || config.task == MineTask.NONE) { return; } if (client.currentScreen != null) { return; // 有界面(箱子/死亡界面等)打开时不操作背包 } PlayerEntity player = client.player; ScreenHandler handler = player.currentScreenHandler; if (handler != player.playerScreenHandler) { return; } if (!handler.getCursorStack().isEmpty()) { return; } // 背包接近满才开始丢,避免背包还没满就误丢 if (this.usedSlots(player) < NEARLY_FULL_SLOTS) { return; } Set trash = new HashSet<>(config.getTrashItemList()); if (trash.isEmpty()) { return; } // 收集背包里的垃圾,按物品 id 分组(并记录是否为可放置的方块) Map> groups = new HashMap<>(); Map blockLike = new HashMap<>(); for (int i = 0; i < 36; i++) { ItemStack s = player.getInventory().main.get(i); if (s.isEmpty()) { continue; } String id = this.itemId(s); if (!trash.contains(id)) { continue; } groups.computeIfAbsent(id, k -> new ArrayList<>()).add(s); blockLike.putIfAbsent(id, s.getItem() instanceof BlockItem); } if (groups.isEmpty()) { return; } // 决定丢弃哪些:方块类垃圾保留 trashReserve 个(搭路用),非方块全丢 List drops = new ArrayList<>(); for (Map.Entry> e : groups.entrySet()) { int keep = Boolean.TRUE.equals(blockLike.get(e.getKey())) ? config.trashReserve : 0; for (ItemStack s : e.getValue()) { if (keep > 0) { keep -= s.getCount(); continue; // 还在保留额度内 } drops.add(s); } } if (drops.isEmpty()) { // 满背但垃圾全在保留额度内:只丢一组腾出空间,尽量保住搭路方块 for (List list : groups.values()) { if (!list.isEmpty()) { drops.add(list.get(0)); break; } } } // 执行丢弃(每 tick 限量,剩余下 tick 继续) int dropped = 0; for (ItemStack s : drops) { if (dropped >= MAX_DROPS_PER_TICK) { break; } int slot = this.screenSlotOf(handler, s); if (slot < 0) { continue; } // THROW + 按钮 1 = 丢出整组 client.interactionManager.clickSlot(handler.syncId, slot, 1, SlotActionType.THROW, player); dropped++; } } /** 统计背包已用格数(快捷栏 + 主背包 0-35,共 36 格)。 */ private int usedSlots(PlayerEntity player) { int used = 0; for (int i = 0; i < 36; i++) { if (!player.getInventory().main.get(i).isEmpty()) { used++; } } return used; } /** 按对象引用在 ScreenHandler 里找槽位下标。 */ private int screenSlotOf(ScreenHandler handler, ItemStack stack) { for (int i = 0; i < handler.slots.size(); i++) { if (handler.getSlot(i).getStack() == stack) { return i; } } return -1; } private String itemId(ItemStack stack) { return Registries.ITEM.getId(stack.getItem()).toString(); } }