新增 AutoGear 服务: - 护甲/工具耐久过低时自动从背包换上同类备用装备(工具按镐/斧/锹/锄/剑类别, 护甲按头/胸/腿/脚部位匹配),选剩余耐久最高的候选 - 通过 clickSlot 三段点击交换(拿起旧装备->与背包新装备互换->就位), 走正规网络包,客户端/服务端同步,单人/多人均可用 - 控制器改为换装优先:能换就不打断挖矿,无备用才暂停/下界返程 - 装备保护状态下持续尝试换装,换上后自动恢复任务 新增 AutoTrash 服务: - 背包满时自动丢弃 trashItems 清单里的垃圾(默认:圆石/深板岩/凝灰岩/ 泥土/花岗岩/闪长岩/安山岩/下界岩/黑石/玄武岩,GUI 可改) - 方块类垃圾保留 trashReserve 个(默认 64)供 Baritone 搭路垫脚; 满背且无其他垃圾可丢时只丢一组腾位,尽量保住搭路方块 - clickSlot(THROW) 丢整组,每 tick 限 4 组防数据包风暴 配套修复: - NetherMiner: 修复血量低时每帧 cancelAll 导致回程寻路反复被取消卡死的 bug; markGearBroken 现在真正触发 RETURN_PORTAL 阶段;产物计数改用注册表 id + 矿物->掉落物映射 + 任务开始基线(老库存不再被误计为战果) - 两个 Miner 的背包满判定加 20 tick 去抖,给自动丢垃圾留出腾位时间 - GUI 新增两个开关与垃圾清单编辑区,切换任务/开关时保留输入框未应用的编辑 - 装备低耐久检查的手持工具仅限真正的工具,不再误伤钓鱼竿等杂物
140 行
5.0 KiB
Java
140 行
5.0 KiB
Java
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;
|
|
|
|
/**
|
|
* 背包垃圾自动清理服务。
|
|
*
|
|
* <p>背包满时,把 {@code trashItems} 清单里的垃圾物品丢出背包(圆石、凝灰岩、
|
|
* 深板岩等挖掘副产物)。方块类垃圾保留 {@code trashReserve} 个供 Baritone
|
|
* 搭路/垫脚使用;若满背时额度之外没有别的垃圾可丢,则只丢一组腾出空间,
|
|
* 尽量保住搭路方块。丢弃通过 {@code clickSlot(THROW)} 走正规网络包,
|
|
* 单人/多人均可用。</p>
|
|
*/
|
|
public final class AutoTrash {
|
|
|
|
/** 每 tick 最多丢弃的物品组数,避免一次产生过多数据包 */
|
|
private static final int MAX_DROPS_PER_TICK = 4;
|
|
|
|
/** 每帧调用:背包满时自动丢弃垃圾物品。 */
|
|
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.inventoryFull(player)) {
|
|
return;
|
|
}
|
|
Set<String> trash = new HashSet<>(config.getTrashItemList());
|
|
if (trash.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// 收集背包里的垃圾,按物品 id 分组(并记录是否为可放置的方块)
|
|
Map<String, List<ItemStack>> groups = new HashMap<>();
|
|
Map<String, Boolean> 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<ItemStack> drops = new ArrayList<>();
|
|
for (Map.Entry<String, List<ItemStack>> 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<ItemStack> 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++;
|
|
}
|
|
}
|
|
|
|
private boolean inventoryFull(PlayerEntity player) {
|
|
for (int i = 0; i < 36; i++) {
|
|
if (player.getInventory().main.get(i).isEmpty()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** 按对象引用在 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();
|
|
}
|
|
} |