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 新增两个开关与垃圾清单编辑区,切换任务/开关时保留输入框未应用的编辑 - 装备低耐久检查的手持工具仅限真正的工具,不再误伤钓鱼竿等杂物
这个提交包含在:
+1
-1
@@ -7,7 +7,7 @@ yarn_mappings=1.21.4+build.8
|
|||||||
loader_version=0.19.3
|
loader_version=0.19.3
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version=2.0.1
|
mod_version=2.1.0
|
||||||
maven_group=autodig
|
maven_group=autodig
|
||||||
archives_base_name=autodig
|
archives_base_name=autodig
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ public final class ConfigStore {
|
|||||||
if (c.netherOres == null || c.netherOres.isBlank()) {
|
if (c.netherOres == null || c.netherOres.isBlank()) {
|
||||||
c.netherOres = "ancient_debris";
|
c.netherOres = "ancient_debris";
|
||||||
}
|
}
|
||||||
|
c.trashReserve = clamp(c.trashReserve, 0, 2304);
|
||||||
|
if (c.trashItems == null || c.trashItems.isBlank()) {
|
||||||
|
c.trashItems = "cobblestone,cobbled_deepslate,deepslate,tuff,dirt,granite,diorite,andesite,netherrack,blackstone,basalt";
|
||||||
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,16 @@ public class DigConfig {
|
|||||||
/** 工具耐久阈值(百分比 0-100),低于此值停止挖矿 */
|
/** 工具耐久阈值(百分比 0-100),低于此值停止挖矿 */
|
||||||
public int toolDurabilityThreshold = 15;
|
public int toolDurabilityThreshold = 15;
|
||||||
|
|
||||||
|
// ---- 装备与背包管理 ----
|
||||||
|
/** 装备耐久过低时自动从背包换上同类备用装备(工具按类别、护甲按部位),无备用才暂停 */
|
||||||
|
public boolean autoGearSwap = true;
|
||||||
|
/** 背包满时自动丢弃垃圾物品(按 trashItems 清单) */
|
||||||
|
public boolean autoTrash = true;
|
||||||
|
/** 垃圾物品清单(逗号分隔的物品 id,如 cobblestone,tuff,deepslate) */
|
||||||
|
public String trashItems = "cobblestone,cobbled_deepslate,deepslate,tuff,dirt,granite,diorite,andesite,netherrack,blackstone,basalt";
|
||||||
|
/** 方块类垃圾保留数量(供 Baritone 搭路/垫脚),0 为不保留 */
|
||||||
|
public int trashReserve = 64;
|
||||||
|
|
||||||
// ---- 岩浆/障碍 ----
|
// ---- 岩浆/障碍 ----
|
||||||
/** 优先绕开岩浆池(给岩浆方块额外寻路代价) */
|
/** 优先绕开岩浆池(给岩浆方块额外寻路代价) */
|
||||||
public boolean avoidLava = true;
|
public boolean avoidLava = true;
|
||||||
@@ -81,6 +91,11 @@ public class DigConfig {
|
|||||||
return parseOreList(this.netherOres);
|
return parseOreList(this.netherOres);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取垃圾物品清单 */
|
||||||
|
public List<String> getTrashItemList() {
|
||||||
|
return parseOreList(this.trashItems);
|
||||||
|
}
|
||||||
|
|
||||||
private static List<String> parseOreList(String str) {
|
private static List<String> parseOreList(String str) {
|
||||||
if (str == null || str.isBlank()) {
|
if (str == null || str.isBlank()) {
|
||||||
return List.of();
|
return List.of();
|
||||||
@@ -103,6 +118,10 @@ public class DigConfig {
|
|||||||
c.armorDurabilityThreshold = this.armorDurabilityThreshold;
|
c.armorDurabilityThreshold = this.armorDurabilityThreshold;
|
||||||
c.toolDurabilityProtection = this.toolDurabilityProtection;
|
c.toolDurabilityProtection = this.toolDurabilityProtection;
|
||||||
c.toolDurabilityThreshold = this.toolDurabilityThreshold;
|
c.toolDurabilityThreshold = this.toolDurabilityThreshold;
|
||||||
|
c.autoGearSwap = this.autoGearSwap;
|
||||||
|
c.autoTrash = this.autoTrash;
|
||||||
|
c.trashItems = this.trashItems;
|
||||||
|
c.trashReserve = this.trashReserve;
|
||||||
c.avoidLava = this.avoidLava;
|
c.avoidLava = this.avoidLava;
|
||||||
c.killAura = this.killAura;
|
c.killAura = this.killAura;
|
||||||
c.killAuraSafe = this.killAuraSafe;
|
c.killAuraSafe = this.killAuraSafe;
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import autodig.client.config.MineTask;
|
|||||||
import autodig.client.miner.NetherMiner;
|
import autodig.client.miner.NetherMiner;
|
||||||
import autodig.client.miner.OverworldMiner;
|
import autodig.client.miner.OverworldMiner;
|
||||||
import autodig.client.service.AutoEat;
|
import autodig.client.service.AutoEat;
|
||||||
|
import autodig.client.service.AutoGear;
|
||||||
|
import autodig.client.service.AutoTrash;
|
||||||
import autodig.client.service.DeathRecorder;
|
import autodig.client.service.DeathRecorder;
|
||||||
import autodig.client.service.KillAura;
|
import autodig.client.service.KillAura;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.entity.player.PlayerInventory;
|
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
@@ -18,7 +19,8 @@ import net.minecraft.world.Heightmap;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 挖矿总控制器:把配置、Baritone 桥接、自动进食、杀戮光环、死亡记录、装备耐久
|
* 挖矿总控制器:把配置、Baritone 桥接、自动进食、杀戮光环、死亡记录、装备耐久
|
||||||
* 保护和各任务专用挖矿器串起来,驱动一个简单的状态机。
|
* 保护(自动换备用装备)、背包垃圾清理和各任务专用挖矿器串起来,
|
||||||
|
* 驱动一个简单的状态机。
|
||||||
*/
|
*/
|
||||||
public final class DigController {
|
public final class DigController {
|
||||||
|
|
||||||
@@ -30,6 +32,8 @@ public final class DigController {
|
|||||||
private final BaritoneBridge bridge = new BaritoneBridge();
|
private final BaritoneBridge bridge = new BaritoneBridge();
|
||||||
private final AutoEat autoEat = new AutoEat();
|
private final AutoEat autoEat = new AutoEat();
|
||||||
private final KillAura killAura = new KillAura();
|
private final KillAura killAura = new KillAura();
|
||||||
|
private final AutoGear autoGear = new AutoGear();
|
||||||
|
private final AutoTrash autoTrash = new AutoTrash();
|
||||||
private final DeathRecorder deathRecorder = new DeathRecorder();
|
private final DeathRecorder deathRecorder = new DeathRecorder();
|
||||||
private final OverworldMiner overworld = new OverworldMiner(this.bridge);
|
private final OverworldMiner overworld = new OverworldMiner(this.bridge);
|
||||||
private final NetherMiner nether = new NetherMiner(this.bridge);
|
private final NetherMiner nether = new NetherMiner(this.bridge);
|
||||||
@@ -52,9 +56,11 @@ public final class DigController {
|
|||||||
}
|
}
|
||||||
PlayerEntity player = client.player;
|
PlayerEntity player = client.player;
|
||||||
|
|
||||||
// 常驻能力:杀戮光环、自动进食
|
// 常驻能力:杀戮光环、自动进食、背包满丢垃圾、换装冷却
|
||||||
this.killAura.tick(config);
|
this.killAura.tick(config);
|
||||||
this.autoEat.tick(config);
|
this.autoEat.tick(config);
|
||||||
|
this.autoTrash.tick(config);
|
||||||
|
this.autoGear.tick();
|
||||||
|
|
||||||
// 死亡处理:死亡瞬间暂停任务,重生后按需回到死亡坐标
|
// 死亡处理:死亡瞬间暂停任务,重生后按需回到死亡坐标
|
||||||
this.deathRecorder.tick(config, this::onRespawn);
|
this.deathRecorder.tick(config, this::onRespawn);
|
||||||
@@ -64,30 +70,37 @@ public final class DigController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 装备耐久保护(仅在挖矿任务进行中检查)
|
// 装备耐久保护(仅在挖矿任务进行中检查)
|
||||||
if (config.task != MineTask.NONE && this.state != State.GEAR_PROTECTION) {
|
if (config.task != MineTask.NONE) {
|
||||||
if (this.isGearLowDurability(config, player)) {
|
boolean gearLow = this.isGearLowDurability(config, player);
|
||||||
this.preGearState = this.state;
|
if (this.state == State.GEAR_PROTECTION) {
|
||||||
this.bridge.cancelAll();
|
// 保护中持续尝试自动换上备用装备,换上后自动恢复任务
|
||||||
// 下界模式:装备耐久低时直接返程回主世界
|
if (config.autoGearSwap && gearLow) {
|
||||||
if (config.task == MineTask.NETHER && config.netherAutoReturn) {
|
this.autoGear.handleLowGear(config, player);
|
||||||
this.nether.markGearBroken();
|
gearLow = this.isGearLowDurability(config, player);
|
||||||
this.setState(State.NETHER, "装备耐久过低,下界返程中");
|
|
||||||
} else {
|
|
||||||
this.setState(State.GEAR_PROTECTION, "装备耐久过低,暂停挖矿保装备");
|
|
||||||
}
|
}
|
||||||
return;
|
if (!gearLow) {
|
||||||
}
|
this.setState(this.preGearState != null ? this.preGearState : State.IDLE,
|
||||||
}
|
"装备耐久已恢复,继续挖矿");
|
||||||
|
this.preGearState = null;
|
||||||
// 装备耐久保护恢复
|
} else {
|
||||||
if (this.state == State.GEAR_PROTECTION) {
|
// 仍在装备保护中,等待换装生效或玩家手动处理
|
||||||
if (!this.isGearLowDurability(config, player)) {
|
return;
|
||||||
this.setState(this.preGearState != null ? this.preGearState : State.IDLE,
|
}
|
||||||
"装备耐久已恢复,继续挖矿");
|
} else if (gearLow) {
|
||||||
this.preGearState = null;
|
// 先尝试自动换上背包里的备用装备,能换就不打断挖矿
|
||||||
} else {
|
if (!(config.autoGearSwap && this.autoGear.handleLowGear(config, player))) {
|
||||||
// 仍在装备保护中,等待玩家换装备
|
this.preGearState = this.state;
|
||||||
return;
|
this.bridge.cancelAll();
|
||||||
|
// 下界模式:无备用装备时直接返程回主世界
|
||||||
|
if (config.task == MineTask.NETHER && config.netherAutoReturn) {
|
||||||
|
this.nether.markGearBroken();
|
||||||
|
this.setState(State.NETHER, "装备耐久过低且无备用装备,下界返程中");
|
||||||
|
} else {
|
||||||
|
this.setState(State.GEAR_PROTECTION, "装备耐久过低且无备用装备,暂停挖矿保装备");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 换装成功:继续正常流程
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +331,7 @@ public final class DigController {
|
|||||||
/**
|
/**
|
||||||
* 检查装备耐久是否过低。
|
* 检查装备耐久是否过低。
|
||||||
* 护甲:任意一件护甲耐久百分比低于阈值
|
* 护甲:任意一件护甲耐久百分比低于阈值
|
||||||
* 工具:手持工具耐久百分比低于阈值
|
* 工具:手持工具(镐/斧/锹/锄/剑)耐久百分比低于阈值
|
||||||
*/
|
*/
|
||||||
private boolean isGearLowDurability(DigConfig config, PlayerEntity player) {
|
private boolean isGearLowDurability(DigConfig config, PlayerEntity player) {
|
||||||
// 护甲耐久检查
|
// 护甲耐久检查
|
||||||
@@ -335,10 +348,10 @@ public final class DigController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 手持工具耐久检查
|
// 手持工具耐久检查(只关心真正的工具,不误伤钓鱼竿/打火石等杂物)
|
||||||
if (config.toolDurabilityProtection) {
|
if (config.toolDurabilityProtection) {
|
||||||
ItemStack mainHand = player.getMainHandStack();
|
ItemStack mainHand = player.getMainHandStack();
|
||||||
if (!mainHand.isEmpty() && mainHand.isDamageable()) {
|
if (AutoGear.isTool(mainHand) && mainHand.isDamageable()) {
|
||||||
int maxDmg = mainHand.getMaxDamage();
|
int maxDmg = mainHand.getMaxDamage();
|
||||||
int remaining = maxDmg - mainHand.getDamage();
|
int remaining = maxDmg - mainHand.getDamage();
|
||||||
double percent = (double) remaining / maxDmg * 100.0;
|
double percent = (double) remaining / maxDmg * 100.0;
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ public final class AutoDigGui extends Screen {
|
|||||||
|
|
||||||
private TextFieldWidget overworldOresField;
|
private TextFieldWidget overworldOresField;
|
||||||
private TextFieldWidget netherOresField;
|
private TextFieldWidget netherOresField;
|
||||||
|
private TextFieldWidget trashField;
|
||||||
|
|
||||||
public AutoDigGui(AutoDigModClient mod) {
|
public AutoDigGui(AutoDigModClient mod) {
|
||||||
super(Text.literal("AutoDig"));
|
super(Text.literal("AutoDig"));
|
||||||
@@ -72,6 +73,7 @@ public final class AutoDigGui extends Screen {
|
|||||||
this.scrollOffset = 0;
|
this.scrollOffset = 0;
|
||||||
this.overworldOresField = null;
|
this.overworldOresField = null;
|
||||||
this.netherOresField = null;
|
this.netherOresField = null;
|
||||||
|
this.trashField = null;
|
||||||
|
|
||||||
// 面板宽度自适应窗口,最小 200 保证 +/- 与输入框可用
|
// 面板宽度自适应窗口,最小 200 保证 +/- 与输入框可用
|
||||||
this.panelW = MathHelper.clamp(this.width - 16, 200, 400);
|
this.panelW = MathHelper.clamp(this.width - 16, 200, 400);
|
||||||
@@ -111,6 +113,12 @@ public final class AutoDigGui extends Screen {
|
|||||||
"下界协同杀戮光环", () -> this.config.killAuraInNether, v -> this.config.killAuraInNether = v);
|
"下界协同杀戮光环", () -> this.config.killAuraInNether, v -> this.config.killAuraInNether = v);
|
||||||
y = this.checkPair(y, "护甲耐久保护", () -> this.config.armorDurabilityProtection, v -> this.config.armorDurabilityProtection = v,
|
y = this.checkPair(y, "护甲耐久保护", () -> this.config.armorDurabilityProtection, v -> this.config.armorDurabilityProtection = v,
|
||||||
"工具耐久保护", () -> this.config.toolDurabilityProtection, v -> this.config.toolDurabilityProtection = v);
|
"工具耐久保护", () -> this.config.toolDurabilityProtection, v -> this.config.toolDurabilityProtection = v);
|
||||||
|
y = this.checkPair(y, "自动换备用装备", () -> this.config.autoGearSwap, v -> this.config.autoGearSwap = v,
|
||||||
|
"背包满丢垃圾", () -> this.config.autoTrash, v -> {
|
||||||
|
this.syncFields(); // 保留输入框中未应用的编辑
|
||||||
|
this.config.autoTrash = v;
|
||||||
|
this.init(); // 切换后显示/隐藏垃圾设置区
|
||||||
|
});
|
||||||
y += 10;
|
y += 10;
|
||||||
|
|
||||||
y = this.header(y, "装备耐久阈值(低于即停,百分比)");
|
y = this.header(y, "装备耐久阈值(低于即停,百分比)");
|
||||||
@@ -120,6 +128,15 @@ public final class AutoDigGui extends Screen {
|
|||||||
v -> this.config.toolDurabilityThreshold = v, 1, 100);
|
v -> this.config.toolDurabilityThreshold = v, 1, 100);
|
||||||
y += 10;
|
y += 10;
|
||||||
|
|
||||||
|
if (this.config.autoTrash) {
|
||||||
|
y = this.header(y, "垃圾丢弃设置");
|
||||||
|
y = this.numberRow(y, "方块垃圾保留量", () -> this.config.trashReserve,
|
||||||
|
v -> this.config.trashReserve = v, 0, 2304);
|
||||||
|
y = this.fieldRow(y, "垃圾物品(逗号分隔的物品 id)",
|
||||||
|
this.config.trashItems, tf -> this.trashField = tf);
|
||||||
|
y += 10;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.config.task == MineTask.OVERWORLD) {
|
if (this.config.task == MineTask.OVERWORLD) {
|
||||||
y = this.header(y, "主世界设置");
|
y = this.header(y, "主世界设置");
|
||||||
y = this.numberRow(y, "挖掘层 (Y)", () -> this.config.overworldLevel,
|
y = this.numberRow(y, "挖掘层 (Y)", () -> this.config.overworldLevel,
|
||||||
@@ -226,6 +243,7 @@ public final class AutoDigGui extends Screen {
|
|||||||
boolean selected = this.config.task == task;
|
boolean selected = this.config.task == task;
|
||||||
String text = selected ? "■ " + label : "□ " + label;
|
String text = selected ? "■ " + label : "□ " + label;
|
||||||
return this.addDrawableChild(this.textButton(text, b -> {
|
return this.addDrawableChild(this.textButton(text, b -> {
|
||||||
|
this.syncFields(); // 保留输入框中未应用的编辑
|
||||||
this.config.task = task;
|
this.config.task = task;
|
||||||
this.init(); // 切换任务:重建滚动区,只显示相关设置
|
this.init(); // 切换任务:重建滚动区,只显示相关设置
|
||||||
}, x, y, w));
|
}, x, y, w));
|
||||||
@@ -339,6 +357,9 @@ public final class AutoDigGui extends Screen {
|
|||||||
if (this.netherOresField != null) {
|
if (this.netherOresField != null) {
|
||||||
this.config.netherOres = this.netherOresField.getText();
|
this.config.netherOres = this.netherOresField.getText();
|
||||||
}
|
}
|
||||||
|
if (this.trashField != null) {
|
||||||
|
this.config.trashItems = this.trashField.getText();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 把界面临时配置写回 mod 全局配置并保存(供 controller 实时读取)。 */
|
/** 把界面临时配置写回 mod 全局配置并保存(供 controller 实时读取)。 */
|
||||||
@@ -364,6 +385,10 @@ public final class AutoDigGui extends Screen {
|
|||||||
target.armorDurabilityThreshold = this.config.armorDurabilityThreshold;
|
target.armorDurabilityThreshold = this.config.armorDurabilityThreshold;
|
||||||
target.toolDurabilityProtection = this.config.toolDurabilityProtection;
|
target.toolDurabilityProtection = this.config.toolDurabilityProtection;
|
||||||
target.toolDurabilityThreshold = this.config.toolDurabilityThreshold;
|
target.toolDurabilityThreshold = this.config.toolDurabilityThreshold;
|
||||||
|
target.autoGearSwap = this.config.autoGearSwap;
|
||||||
|
target.autoTrash = this.config.autoTrash;
|
||||||
|
target.trashItems = this.config.trashItems;
|
||||||
|
target.trashReserve = this.config.trashReserve;
|
||||||
this.mod.saveConfig();
|
this.mod.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ package autodig.client.miner;
|
|||||||
|
|
||||||
import autodig.client.bridge.BaritoneBridge;
|
import autodig.client.bridge.BaritoneBridge;
|
||||||
import autodig.client.config.DigConfig;
|
import autodig.client.config.DigConfig;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
import net.minecraft.registry.RegistryKey;
|
import net.minecraft.registry.RegistryKey;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
@@ -16,7 +21,8 @@ import net.minecraft.world.World;
|
|||||||
* 死亡重生的返回逻辑由控制器统一处理,本类每次 tick 依据当前所在维度
|
* 死亡重生的返回逻辑由控制器统一处理,本类每次 tick 依据当前所在维度
|
||||||
* 自适应地执行对应阶段。</p>
|
* 自适应地执行对应阶段。</p>
|
||||||
*
|
*
|
||||||
* <p>下界无死亡不掉落,比主世界更"惜命":血量低或装备耐久低时主动触发返程。</p>
|
* <p>下界无死亡不掉落,比主世界更"惜命":血量低或装备耐久低时主动触发返程。
|
||||||
|
* 背包满采用去抖判定(连续 1 秒都满才算真满),给自动丢垃圾服务留出腾位时间。</p>
|
||||||
*/
|
*/
|
||||||
public final class NetherMiner {
|
public final class NetherMiner {
|
||||||
|
|
||||||
@@ -33,6 +39,31 @@ public final class NetherMiner {
|
|||||||
RETURN_PORTAL
|
RETURN_PORTAL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 背包满去抖阈值(tick):连续这么久都满才认定为真满 */
|
||||||
|
private static final int FULL_DEBOUNCE_TICKS = 20;
|
||||||
|
|
||||||
|
/** 常见矿物方块 id -> 掉落物品 id(挖掘产物与方块本身不同的情况) */
|
||||||
|
private static final Map<String, String> ORE_TO_DROP = Map.ofEntries(
|
||||||
|
Map.entry("diamond_ore", "diamond"),
|
||||||
|
Map.entry("deepslate_diamond_ore", "diamond"),
|
||||||
|
Map.entry("emerald_ore", "emerald"),
|
||||||
|
Map.entry("deepslate_emerald_ore", "emerald"),
|
||||||
|
Map.entry("lapis_ore", "lapis_lazuli"),
|
||||||
|
Map.entry("deepslate_lapis_ore", "lapis_lazuli"),
|
||||||
|
Map.entry("redstone_ore", "redstone"),
|
||||||
|
Map.entry("deepslate_redstone_ore", "redstone"),
|
||||||
|
Map.entry("coal_ore", "coal"),
|
||||||
|
Map.entry("deepslate_coal_ore", "coal"),
|
||||||
|
Map.entry("copper_ore", "raw_copper"),
|
||||||
|
Map.entry("deepslate_copper_ore", "raw_copper"),
|
||||||
|
Map.entry("iron_ore", "raw_iron"),
|
||||||
|
Map.entry("deepslate_iron_ore", "raw_iron"),
|
||||||
|
Map.entry("gold_ore", "raw_gold"),
|
||||||
|
Map.entry("deepslate_gold_ore", "raw_gold"),
|
||||||
|
Map.entry("nether_gold_ore", "gold_nugget"),
|
||||||
|
Map.entry("nether_quartz_ore", "quartz"));
|
||||||
|
// ancient_debris 挖掉落后就是 ancient_debris 物品,走默认映射
|
||||||
|
|
||||||
private Stage stage = Stage.GO_COMMIT;
|
private Stage stage = Stage.GO_COMMIT;
|
||||||
private boolean done;
|
private boolean done;
|
||||||
/** 结束原因是否为「背包已满」 */
|
/** 结束原因是否为「背包已满」 */
|
||||||
@@ -41,12 +72,19 @@ public final class NetherMiner {
|
|||||||
private boolean fled;
|
private boolean fled;
|
||||||
/** 结束原因是否为「装备耐久过低触发返程」 */
|
/** 结束原因是否为「装备耐久过低触发返程」 */
|
||||||
private boolean gearBroken;
|
private boolean gearBroken;
|
||||||
|
/** 外部(控制器)要求的强制返程 */
|
||||||
|
private boolean forceReturn;
|
||||||
/** 是否已派发过挖矿指令(用于区分「尚未开始」和「已经挖完」) */
|
/** 是否已派发过挖矿指令(用于区分「尚未开始」和「已经挖完」) */
|
||||||
private boolean miningStarted;
|
private boolean miningStarted;
|
||||||
/** 派发节流 */
|
/** 派发节流 */
|
||||||
private long lastIssue;
|
private long lastIssue;
|
||||||
/** 已挖到的目标矿物数量(通过扫描背包近似) */
|
/** 已挖到的目标矿物数量(按背包产物计数,扣除任务开始时的基线) */
|
||||||
private int minedCount;
|
private int minedCount;
|
||||||
|
/** 任务开始时背包里已有的目标产物数量(避免老库存被误计为战果) */
|
||||||
|
private int baselineCount;
|
||||||
|
private boolean baselineSet;
|
||||||
|
/** 背包满连续持续的 tick 数(去抖) */
|
||||||
|
private int fullTicks;
|
||||||
|
|
||||||
public NetherMiner(BaritoneBridge bridge) {
|
public NetherMiner(BaritoneBridge bridge) {
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
@@ -59,8 +97,12 @@ public final class NetherMiner {
|
|||||||
this.full = false;
|
this.full = false;
|
||||||
this.fled = false;
|
this.fled = false;
|
||||||
this.gearBroken = false;
|
this.gearBroken = false;
|
||||||
|
this.forceReturn = false;
|
||||||
this.miningStarted = false;
|
this.miningStarted = false;
|
||||||
this.minedCount = 0;
|
this.minedCount = 0;
|
||||||
|
this.baselineCount = 0;
|
||||||
|
this.baselineSet = false;
|
||||||
|
this.fullTicks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,13 +121,14 @@ public final class NetherMiner {
|
|||||||
boolean inOverworld = dim == World.OVERWORLD;
|
boolean inOverworld = dim == World.OVERWORLD;
|
||||||
boolean inNether = dim == World.NETHER;
|
boolean inNether = dim == World.NETHER;
|
||||||
|
|
||||||
// 下界惜命:血量过低时触发返程
|
// 下界惜命:血量过低时触发返程(只触发一次,避免反复取消回程寻路)
|
||||||
if (inNether && config.netherFleeHealthThreshold > 0
|
if (inNether && this.stage != Stage.RETURN_PORTAL && !this.done
|
||||||
|
&& config.netherFleeHealthThreshold > 0
|
||||||
&& this.healthPercent(player) < config.netherFleeHealthThreshold) {
|
&& this.healthPercent(player) < config.netherFleeHealthThreshold) {
|
||||||
this.fled = true;
|
this.fled = true;
|
||||||
this.miningStarted = false;
|
this.miningStarted = false;
|
||||||
this.bridge.cancelAll();
|
|
||||||
if (config.netherAutoReturn) {
|
if (config.netherAutoReturn) {
|
||||||
|
this.bridge.cancelAll();
|
||||||
this.stage = Stage.RETURN_PORTAL;
|
this.stage = Stage.RETURN_PORTAL;
|
||||||
} else {
|
} else {
|
||||||
this.done = true;
|
this.done = true;
|
||||||
@@ -93,6 +136,13 @@ public final class NetherMiner {
|
|||||||
return this.done;
|
return this.done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 外部标记(装备耐久过低等):直接进入返程
|
||||||
|
if (this.forceReturn && inNether && this.stage != Stage.RETURN_PORTAL) {
|
||||||
|
this.miningStarted = false;
|
||||||
|
this.bridge.cancelAll();
|
||||||
|
this.stage = Stage.RETURN_PORTAL;
|
||||||
|
}
|
||||||
|
|
||||||
switch (this.stage) {
|
switch (this.stage) {
|
||||||
case GO_COMMIT -> {
|
case GO_COMMIT -> {
|
||||||
// 选择去哪个维度
|
// 选择去哪个维度
|
||||||
@@ -114,7 +164,20 @@ public final class NetherMiner {
|
|||||||
// 意外回到主世界,重走流程
|
// 意外回到主世界,重走流程
|
||||||
this.stage = Stage.GO_COMMIT;
|
this.stage = Stage.GO_COMMIT;
|
||||||
this.miningStarted = false;
|
this.miningStarted = false;
|
||||||
} else if (this.inventoryFull(player)) {
|
return this.done;
|
||||||
|
}
|
||||||
|
// 记录任务开始时背包已有的产物数量作为基线
|
||||||
|
if (!this.baselineSet) {
|
||||||
|
this.baselineCount = this.countProducts(config, player);
|
||||||
|
this.baselineSet = true;
|
||||||
|
}
|
||||||
|
// 背包满去抖:连续一段时间都满才认定为真满(给自动丢垃圾留时间)
|
||||||
|
if (this.inventoryFull(player)) {
|
||||||
|
this.fullTicks++;
|
||||||
|
} else {
|
||||||
|
this.fullTicks = 0;
|
||||||
|
}
|
||||||
|
if (this.fullTicks >= FULL_DEBOUNCE_TICKS) {
|
||||||
// 背包已满:期望回程则去传送门;不回程时无处可放,直接结束
|
// 背包已满:期望回程则去传送门;不回程时无处可放,直接结束
|
||||||
this.full = true;
|
this.full = true;
|
||||||
this.miningStarted = false;
|
this.miningStarted = false;
|
||||||
@@ -123,7 +186,7 @@ public final class NetherMiner {
|
|||||||
} else {
|
} else {
|
||||||
this.done = true;
|
this.done = true;
|
||||||
}
|
}
|
||||||
} else if (this.minedEnough(config)) {
|
} else if (this.minedEnough(config, player)) {
|
||||||
// 已挖够目标数量
|
// 已挖够目标数量
|
||||||
this.miningStarted = false;
|
this.miningStarted = false;
|
||||||
if (config.netherAutoReturn) {
|
if (config.netherAutoReturn) {
|
||||||
@@ -132,7 +195,7 @@ public final class NetherMiner {
|
|||||||
this.done = true;
|
this.done = true;
|
||||||
}
|
}
|
||||||
} else if (this.miningStarted && !this.bridge.isActive()) {
|
} else if (this.miningStarted && !this.bridge.isActive()) {
|
||||||
// 挖矿进程已跑完(附近无矿可挖)
|
// 挖矿进程已跑完(挖够数量或附近无矿可挖)
|
||||||
this.miningStarted = false;
|
this.miningStarted = false;
|
||||||
if (config.netherAutoReturn) {
|
if (config.netherAutoReturn) {
|
||||||
this.stage = Stage.RETURN_PORTAL;
|
this.stage = Stage.RETURN_PORTAL;
|
||||||
@@ -163,35 +226,40 @@ public final class NetherMiner {
|
|||||||
return this.done;
|
return this.done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否已挖够目标矿物数量 */
|
/** 判断是否已挖够目标矿物数量(按背包产物计数,扣除任务开始时的基线)。 */
|
||||||
private boolean minedEnough(DigConfig config) {
|
private boolean minedEnough(DigConfig config, PlayerEntity player) {
|
||||||
if (config.netherTargetCount <= 0) {
|
if (config.netherTargetCount <= 0) {
|
||||||
return false; // -1 或 0 表示无限挖
|
return false; // -1 或 0 表示无限挖
|
||||||
}
|
}
|
||||||
// 通过扫描背包中目标矿物数量近似判断
|
this.minedCount = this.countProducts(config, player) - this.baselineCount;
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
return this.minedCount >= config.netherTargetCount;
|
||||||
if (client.player == null) {
|
}
|
||||||
return false;
|
|
||||||
|
/** 统计背包中目标矿物产物的总数量(方块 id 映射到掉落物 id)。 */
|
||||||
|
private int countProducts(DigConfig config, PlayerEntity player) {
|
||||||
|
List<String> ores = config.getNetherOreList();
|
||||||
|
if (ores.isEmpty()) {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
int count = 0;
|
Set<String> products = new HashSet<>();
|
||||||
var ores = config.getNetherOreList();
|
for (String ore : ores) {
|
||||||
|
products.add(ORE_TO_DROP.getOrDefault(ore, ore));
|
||||||
|
}
|
||||||
|
int total = 0;
|
||||||
for (int i = 0; i < 36; i++) {
|
for (int i = 0; i < 36; i++) {
|
||||||
var stack = client.player.getInventory().main.get(i);
|
ItemStack stack = player.getInventory().main.get(i);
|
||||||
if (stack.isEmpty()) {
|
if (stack.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 通过物品的 toString 粗略匹配矿物名
|
if (products.contains(itemId(stack))) {
|
||||||
String itemName = stack.getItem().toString();
|
total += stack.getCount();
|
||||||
for (String ore : ores) {
|
|
||||||
// ancient_debris -> ancient_debris, nether_gold_ore -> nether_gold_ore
|
|
||||||
if (itemName.contains(ore) || ore.contains(itemName)) {
|
|
||||||
count += stack.getCount();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.minedCount = count;
|
return total;
|
||||||
return count >= config.netherTargetCount;
|
}
|
||||||
|
|
||||||
|
private static String itemId(ItemStack stack) {
|
||||||
|
return Registries.ITEM.getId(stack.getItem()).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void issue(Runnable action) {
|
private void issue(Runnable action) {
|
||||||
@@ -234,13 +302,14 @@ public final class NetherMiner {
|
|||||||
return this.gearBroken;
|
return this.gearBroken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 标记装备耐久过低,触发返程 */
|
/** 标记装备耐久过低,触发强制返程 */
|
||||||
public void markGearBroken() {
|
public void markGearBroken() {
|
||||||
this.gearBroken = true;
|
this.gearBroken = true;
|
||||||
|
this.forceReturn = true;
|
||||||
this.miningStarted = false;
|
this.miningStarted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return 已挖到的目标矿物数量 */
|
/** @return 本次任务新挖到的目标矿物数量 */
|
||||||
public int getMinedCount() {
|
public int getMinedCount() {
|
||||||
return this.minedCount;
|
return this.minedCount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ public final class OverworldMiner {
|
|||||||
private long lastIssue;
|
private long lastIssue;
|
||||||
/** 上一次打印诊断日志的时间戳,用于节流 */
|
/** 上一次打印诊断日志的时间戳,用于节流 */
|
||||||
private long lastLog;
|
private long lastLog;
|
||||||
|
/** 背包满连续持续的 tick 数(去抖:给自动丢垃圾留出腾位时间) */
|
||||||
|
private int fullTicks;
|
||||||
|
|
||||||
|
/** 背包满去抖阈值(tick):连续这么久都满才认定为真满 */
|
||||||
|
private static final int FULL_DEBOUNCE_TICKS = 20;
|
||||||
|
|
||||||
public OverworldMiner(BaritoneBridge bridge) {
|
public OverworldMiner(BaritoneBridge bridge) {
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
@@ -64,6 +69,7 @@ public final class OverworldMiner {
|
|||||||
this.step = 0;
|
this.step = 0;
|
||||||
this.done = false;
|
this.done = false;
|
||||||
this.full = false;
|
this.full = false;
|
||||||
|
this.fullTicks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<BlockPos> buildPlan(BlockPos base, int y, int trunkLen, int branchLen, int branchGap, int axis, int sign) {
|
private List<BlockPos> buildPlan(BlockPos base, int y, int trunkLen, int branchLen, int branchGap, int axis, int sign) {
|
||||||
@@ -105,11 +111,17 @@ public final class OverworldMiner {
|
|||||||
if (player == null) {
|
if (player == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// 背包满去抖:连续一段时间都满才认定为真满(给自动丢垃圾留出腾位时间)
|
||||||
if (this.inventoryFull(player)) {
|
if (this.inventoryFull(player)) {
|
||||||
this.done = true;
|
this.fullTicks++;
|
||||||
this.full = true;
|
if (this.fullTicks >= FULL_DEBOUNCE_TICKS) {
|
||||||
System.out.println("[AutoDig] overworld 结束:背包已满");
|
this.done = true;
|
||||||
return true;
|
this.full = true;
|
||||||
|
System.out.println("[AutoDig] overworld 结束:背包已满");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.fullTicks = 0;
|
||||||
}
|
}
|
||||||
if (this.step >= this.plan.size()) {
|
if (this.step >= this.plan.size()) {
|
||||||
this.done = true;
|
this.done = true;
|
||||||
@@ -194,6 +206,7 @@ public final class OverworldMiner {
|
|||||||
this.step = best;
|
this.step = best;
|
||||||
this.done = false;
|
this.done = false;
|
||||||
this.full = false;
|
this.full = false;
|
||||||
|
this.fullTicks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
package autodig.client.service;
|
||||||
|
|
||||||
|
import autodig.client.config.DigConfig;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.network.ClientPlayerInteractionManager;
|
||||||
|
import net.minecraft.entity.EquipmentSlot;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
|
import net.minecraft.item.ArmorItem;
|
||||||
|
import net.minecraft.item.AxeItem;
|
||||||
|
import net.minecraft.item.HoeItem;
|
||||||
|
import net.minecraft.item.Item;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.item.PickaxeItem;
|
||||||
|
import net.minecraft.item.ShovelItem;
|
||||||
|
import net.minecraft.item.SwordItem;
|
||||||
|
import net.minecraft.screen.ScreenHandler;
|
||||||
|
import net.minecraft.screen.slot.SlotActionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装备自动更换服务。
|
||||||
|
*
|
||||||
|
* <p>当护甲或手持工具耐久过低时,从背包里寻找同类别(工具:镐/斧/锹/锄/剑;
|
||||||
|
* 护甲:同部位头/胸/腿/脚)且剩余耐久更高的备用装备,通过对玩家背包
|
||||||
|
* ScreenHandler 的三次点击完成交换:拿起旧装备 → 与背包中的新装备互换 →
|
||||||
|
* 新装备就位。交换走正规网络包(clickSlot),客户端/服务端自动同步,
|
||||||
|
* 单人/多人均可用。找不到备用装备时返回 false,由控制器走"暂停保装备"或
|
||||||
|
* "下界返程"逻辑。</p>
|
||||||
|
*/
|
||||||
|
public final class AutoGear {
|
||||||
|
|
||||||
|
/** 每次换装后的冷却 tick 数:等待客户端预测/服务端确认生效,避免连续误判 */
|
||||||
|
private static final int SWAP_COOLDOWN_TICKS = 10;
|
||||||
|
|
||||||
|
private int coolTicks;
|
||||||
|
|
||||||
|
/** 每帧推进冷却。 */
|
||||||
|
public void tick() {
|
||||||
|
if (this.coolTicks > 0) {
|
||||||
|
this.coolTicks--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试处理低耐久装备:用背包里的备用装备换下低耐久装备。
|
||||||
|
*
|
||||||
|
* @return true 表示本次(或最近一次)已成功换装,调用方可继续挖矿;
|
||||||
|
* false 表示当前没有可用的备用装备
|
||||||
|
*/
|
||||||
|
public boolean handleLowGear(DigConfig config, PlayerEntity player) {
|
||||||
|
if (this.coolTicks > 0) {
|
||||||
|
return true; // 刚换过装,等待生效
|
||||||
|
}
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
if (client.interactionManager == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 只在玩家背包界面(未打开箱子等容器)且鼠标上没拿着物品时操作
|
||||||
|
if (player.currentScreenHandler != player.playerScreenHandler) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!player.currentScreenHandler.getCursorStack().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PlayerInventory inv = player.getInventory();
|
||||||
|
|
||||||
|
// 1. 手持工具:同类别里找剩余耐久最高的备用工具
|
||||||
|
ItemStack held = player.getMainHandStack();
|
||||||
|
if (isTool(held) && held.isDamageable()
|
||||||
|
&& this.durabilityPct(held) < config.toolDurabilityThreshold) {
|
||||||
|
ItemStack repl = this.findToolReplacement(inv, held);
|
||||||
|
if (repl != null && this.swap(player, held, repl)) {
|
||||||
|
this.coolTicks = SWAP_COOLDOWN_TICKS;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 护甲:逐件找同部位、剩余耐久更高的备用护甲
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
ItemStack piece = inv.armor.get(i);
|
||||||
|
if (piece.isEmpty() || !piece.isDamageable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (this.durabilityPct(piece) >= config.armorDurabilityThreshold) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ItemStack repl = this.findArmorReplacement(player, inv, i, piece);
|
||||||
|
if (repl != null && this.swap(player, piece, repl)) {
|
||||||
|
this.coolTicks = SWAP_COOLDOWN_TICKS;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 手持工具的替换候选:同类别中剩余耐久最高且高于当前手持的。 */
|
||||||
|
private ItemStack findToolReplacement(PlayerInventory inv, ItemStack held) {
|
||||||
|
String kind = toolKind(held.getItem());
|
||||||
|
if (kind == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ItemStack best = null;
|
||||||
|
for (int i = 0; i < 36; i++) {
|
||||||
|
if (i == inv.selectedSlot) {
|
||||||
|
continue; // 跳过当前手持槽位
|
||||||
|
}
|
||||||
|
ItemStack s = inv.main.get(i);
|
||||||
|
if (s.isEmpty() || !s.isDamageable() || !kind.equals(toolKind(s.getItem()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (best == null || this.remaining(s) > this.remaining(best)) {
|
||||||
|
best = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best != null && this.remaining(best) > this.remaining(held) ? best : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 护甲替换候选:同部位中剩余耐久最高且高于当前穿戴的。 */
|
||||||
|
private ItemStack findArmorReplacement(PlayerEntity player, PlayerInventory inv, int armorIndex, ItemStack current) {
|
||||||
|
ItemStack best = null;
|
||||||
|
for (int i = 0; i < 36; i++) {
|
||||||
|
ItemStack s = inv.main.get(i);
|
||||||
|
if (s.isEmpty() || !(s.getItem() instanceof ArmorItem)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (this.armorSlotIndex(player, s) != armorIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (best == null || this.remaining(s) > this.remaining(best)) {
|
||||||
|
best = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best != null && this.remaining(best) > this.remaining(current) ? best : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 背包里的护甲对应的护甲槽位下标(0=鞋 1=腿 2=胸 3=头),不可穿戴返回 -1。 */
|
||||||
|
private int armorSlotIndex(PlayerEntity player, ItemStack stack) {
|
||||||
|
EquipmentSlot slot = player.getPreferredEquipmentSlot(stack);
|
||||||
|
if (slot != EquipmentSlot.HEAD && slot != EquipmentSlot.CHEST
|
||||||
|
&& slot != EquipmentSlot.LEGS && slot != EquipmentSlot.FEET) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return slot.getEntitySlotId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 三次点击完成装备交换:
|
||||||
|
* 点击旧装备槽(拿起)→ 点击背包新装备槽(互换)→ 再点旧装备槽(新装备就位)。
|
||||||
|
*/
|
||||||
|
private boolean swap(PlayerEntity player, ItemStack equipped, ItemStack replacement) {
|
||||||
|
ScreenHandler handler = player.currentScreenHandler;
|
||||||
|
int equippedSlot = this.screenSlotOf(handler, equipped);
|
||||||
|
int bagSlot = this.screenSlotOf(handler, replacement);
|
||||||
|
if (equippedSlot < 0 || bagSlot < 0 || equippedSlot == bagSlot) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ClientPlayerInteractionManager im = MinecraftClient.getInstance().interactionManager;
|
||||||
|
int syncId = handler.syncId;
|
||||||
|
im.clickSlot(syncId, equippedSlot, 0, SlotActionType.PICKUP, player); // 拿起旧装备
|
||||||
|
im.clickSlot(syncId, bagSlot, 0, SlotActionType.PICKUP, player); // 与背包新装备互换
|
||||||
|
im.clickSlot(syncId, equippedSlot, 0, SlotActionType.PICKUP, player); // 新装备就位
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 是否为受耐久保护的工具(镐/斧/锹/锄/剑)。 */
|
||||||
|
public static boolean isTool(ItemStack stack) {
|
||||||
|
return !stack.isEmpty() && toolKind(stack.getItem()) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toolKind(Item item) {
|
||||||
|
if (item instanceof PickaxeItem) return "pickaxe";
|
||||||
|
if (item instanceof AxeItem) return "axe";
|
||||||
|
if (item instanceof ShovelItem) return "shovel";
|
||||||
|
if (item instanceof HoeItem) return "hoe";
|
||||||
|
if (item instanceof SwordItem) return "sword";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int remaining(ItemStack stack) {
|
||||||
|
return stack.getMaxDamage() - stack.getDamage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double durabilityPct(ItemStack stack) {
|
||||||
|
return stack.getMaxDamage() <= 0 ? 100.0 : (double) remaining(stack) / stack.getMaxDamage() * 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"id": "autodig",
|
"id": "autodig",
|
||||||
"version": "${version}",
|
"version": "${version}",
|
||||||
"name": "AutoDig",
|
"name": "AutoDig",
|
||||||
"description": "自动挖矿模组(内置 Baritone 1.13.1,无需另行安装):主世界模式与下界模式,均可自定义目标矿物。主世界鱼骨巷道挖矿,下界自动进出传送门挖矿并支持返程。支持自动切换工具、自动进食、杀戮光环、保命优先、优先绕开岩浆、装备耐久保护、死亡重生回点。",
|
"description": "自动挖矿模组(内置 Baritone 1.13.1,无需另行安装):主世界模式与下界模式,均可自定义目标矿物。主世界鱼骨巷道挖矿,下界自动进出传送门挖矿并支持返程。支持自动切换工具、自动进食、杀戮光环、保命优先、优先绕开岩浆、装备耐久保护(自动换备用装备)、背包满自动丢垃圾、死亡重生回点。",
|
||||||
"authors": [],
|
"authors": [],
|
||||||
"contact": {},
|
"contact": {},
|
||||||
"license": "All-Rights-Reserved",
|
"license": "All-Rights-Reserved",
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户