feat: 自动换备用装备 + 背包满自动丢垃圾 (v2.1.0)
新增 AutoGear 服务: - 护甲/工具耐久过低时自动从背包换上同类备用装备(工具按镐/斧/锹/锄/剑类别, 护甲按头/胸/腿/脚部位匹配),选剩余耐久最高的候选 - 通过 clickSlot 三段点击交换(拿起旧装备->与背包新装备互换->就位), 走正规网络包,客户端/服务端同步,单人/多人均可用 - 控制器改为换装优先:能换就不打断挖矿,无备用才暂停/下界返程 - 装备保护状态下持续尝试换装,换上后自动恢复任务 新增 AutoTrash 服务: - 背包满时自动丢弃 trashItems 清单里的垃圾(默认:圆石/深板岩/凝灰岩/ 泥土/花岗岩/闪长岩/安山岩/下界岩/黑石/玄武岩,GUI 可改) - 方块类垃圾保留 trashReserve 个(默认 64)供 Baritone 搭路垫脚; 满背且无其他垃圾可丢时只丢一组腾位,尽量保住搭路方块 - clickSlot(THROW) 丢整组,每 tick 限 4 组防数据包风暴 配套修复: - NetherMiner: 修复血量低时每帧 cancelAll 导致回程寻路反复被取消卡死的 bug; markGearBroken 现在真正触发 RETURN_PORTAL 阶段;产物计数改用注册表 id + 矿物->掉落物映射 + 任务开始基线(老库存不再被误计为战果) - 两个 Miner 的背包满判定加 20 tick 去抖,给自动丢垃圾留出腾位时间 - GUI 新增两个开关与垃圾清单编辑区,切换任务/开关时保留输入框未应用的编辑 - 装备低耐久检查的手持工具仅限真正的工具,不再误伤钓鱼竿等杂物
这个提交包含在:
@@ -0,0 +1,140 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户