feat: 重构挖矿模式为主世界/下界,新增装备耐久保护与自定义矿物

- MineTask: DIAMOND->OVERWORLD, ANCIENT_DEBRIS->NETHER, 移除钻石/远古残骸硬编码
- DigConfig: 新增主世界/下界可选矿物列表、护甲/工具耐久保护阈值、下界返程血量阈值、目标数量
- AutoDigCommands: 支持 overworld/nether/none 任务名
- OverworldMiner: 重命名自 FishboneMiner, 支持配置矿物列表
- NetherMiner: 重命名自 DebrisHandler, 支持配置矿物、血量/耐久/背包满自动返程
- DigController: 新增 GEAR_PROTECTION 状态, 装备耐久过低暂停挖矿/触发下界返程
- AutoDigGui: 更新任务按钮、矿物预设循环、装备耐久/返程阈值设置
- ConfigStore: 扩展 sanitize 校验新字段
- 版本升级: 1.0.0 -> 2.0.0
这个提交包含在:
lyizai
2026-08-29 13:23:43 +08:00
父节点 95c4e97db0
当前提交 4c081afe06
共修改 17 个文件,包含 864 行新增和 299 行删除
@@ -0,0 +1,247 @@
package autodig.client.miner;
import autodig.client.bridge.BaritoneBridge;
import autodig.client.config.DigConfig;
import java.util.List;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.registry.RegistryKey;
import net.minecraft.world.World;
/**
* 下界自动挖矿流转。
*
* <p>流程:主世界 -> 走进最近传送门 -> 进入下界 -> 挖可配置矿物 ->
* (挖够数量/背包满/血量低/装备耐久低)-> 找下界传送门走回主世界。
* 死亡重生的返回逻辑由控制器统一处理,本类每次 tick 依据当前所在维度
* 自适应地执行对应阶段。</p>
*
* <p>下界无死亡不掉落,比主世界更"惜命":血量低或装备耐久低时主动触发返程。</p>
*/
public final class NetherMiner {
private final BaritoneBridge bridge;
private enum Stage {
/** 在下界从传送门前往主世界(或已在主世界即完成) */
GO_COMMIT,
/** 在前一个维度前往传送门 */
GO_PORTAL,
/** 挖矿 */
MINE,
/** 返回主世界 */
RETURN_PORTAL
}
private Stage stage = Stage.GO_COMMIT;
private boolean done;
/** 结束原因是否为「背包已满」 */
private boolean full;
/** 结束原因是否为「血量过低触发返程」 */
private boolean fled;
/** 结束原因是否为「装备耐久过低触发返程」 */
private boolean gearBroken;
/** 是否已派发过挖矿指令(用于区分「尚未开始」和「已经挖完」) */
private boolean miningStarted;
/** 派发节流 */
private long lastIssue;
/** 已挖到的目标矿物数量(通过扫描背包近似) */
private int minedCount;
public NetherMiner(BaritoneBridge bridge) {
this.bridge = bridge;
}
/** 任务结束或需要切换任务时归还控制权。 */
public void reset() {
this.stage = Stage.GO_COMMIT;
this.done = false;
this.full = false;
this.fled = false;
this.gearBroken = false;
this.miningStarted = false;
this.minedCount = 0;
}
/**
* 每帧推进下界挖矿流转。
*
* @param config 挖矿配置
* @return 是否已全部完成(含回到主世界)
*/
public boolean tick(DigConfig config) {
MinecraftClient client = MinecraftClient.getInstance();
if (client.player == null || client.world == null) {
return this.done;
}
PlayerEntity player = client.player;
RegistryKey<World> dim = client.world.getRegistryKey();
boolean inOverworld = dim == World.OVERWORLD;
boolean inNether = dim == World.NETHER;
// 下界惜命:血量过低时触发返程
if (inNether && config.netherFleeHealthThreshold > 0
&& this.healthPercent(player) < config.netherFleeHealthThreshold) {
this.fled = true;
this.miningStarted = false;
this.bridge.cancelAll();
if (config.netherAutoReturn) {
this.stage = Stage.RETURN_PORTAL;
} else {
this.done = true;
}
return this.done;
}
switch (this.stage) {
case GO_COMMIT -> {
// 选择去哪个维度
if (inNether) {
this.stage = Stage.MINE;
} else {
this.stage = Stage.GO_PORTAL;
}
}
case GO_PORTAL -> {
if (inNether) {
this.stage = Stage.MINE; // 已通过传送门进入下界
} else {
this.issue(() -> this.bridge.toNetherPortal());
}
}
case MINE -> {
if (!inNether) {
// 意外回到主世界,重走流程
this.stage = Stage.GO_COMMIT;
this.miningStarted = false;
} else if (this.inventoryFull(player)) {
// 背包已满:期望回程则去传送门;不回程时无处可放,直接结束
this.full = true;
this.miningStarted = false;
if (config.netherAutoReturn) {
this.stage = Stage.RETURN_PORTAL;
} else {
this.done = true;
}
} else if (this.minedEnough(config)) {
// 已挖够目标数量
this.miningStarted = false;
if (config.netherAutoReturn) {
this.stage = Stage.RETURN_PORTAL;
} else {
this.done = true;
}
} else if (this.miningStarted && !this.bridge.isActive()) {
// 挖矿进程已跑完(附近无矿可挖)
this.miningStarted = false;
if (config.netherAutoReturn) {
this.stage = Stage.RETURN_PORTAL;
} else {
this.stage = Stage.GO_COMMIT; // 停在下界继续挖(不返回)
}
} else {
// 先派发挖矿指令;派发成功后 miningStarted 置位,
// 之后才能用 isActive()==false 判定「挖完了」
this.issue(() -> {
List<String> ores = config.getNetherOreList();
if (!ores.isEmpty()) {
int count = config.netherTargetCount > 0 ? config.netherTargetCount : -1;
this.bridge.mineByName(count, ores.toArray(String[]::new));
this.miningStarted = true;
}
});
}
}
case RETURN_PORTAL -> {
if (inOverworld) {
this.done = true; // 已回到主世界,全流程完成
} else {
this.issue(() -> this.bridge.toNetherPortal());
}
}
}
return this.done;
}
/** 判断是否已挖够目标矿物数量 */
private boolean minedEnough(DigConfig config) {
if (config.netherTargetCount <= 0) {
return false; // -1 或 0 表示无限挖
}
// 通过扫描背包中目标矿物数量近似判断
MinecraftClient client = MinecraftClient.getInstance();
if (client.player == null) {
return false;
}
int count = 0;
var ores = config.getNetherOreList();
for (int i = 0; i < 36; i++) {
var stack = client.player.getInventory().main.get(i);
if (stack.isEmpty()) {
continue;
}
// 通过物品的 toString 粗略匹配矿物名
String itemName = stack.getItem().toString();
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 count >= config.netherTargetCount;
}
private void issue(Runnable action) {
long now = System.currentTimeMillis();
if (now - this.lastIssue > 1000) {
action.run();
this.lastIssue = now;
}
}
private boolean inventoryFull(PlayerEntity player) {
for (int i = 0; i < 36; i++) {
if (player.getInventory().main.get(i).isEmpty()) {
return false;
}
}
return true;
}
private double healthPercent(PlayerEntity player) {
return player.getHealth() / Math.max(player.getMaxHealth(), 1.0) * 100.0;
}
public boolean isDone() {
return this.done;
}
/** @return 结束原因是否为「背包已满」 */
public boolean isFull() {
return this.full;
}
/** @return 结束原因是否为「血量过低触发返程」 */
public boolean isFled() {
return this.fled;
}
/** @return 结束原因是否为「装备耐久过低触发返程」 */
public boolean isGearBroken() {
return this.gearBroken;
}
/** 标记装备耐久过低,触发返程 */
public void markGearBroken() {
this.gearBroken = true;
this.miningStarted = false;
}
/** @return 已挖到的目标矿物数量 */
public int getMinedCount() {
return this.minedCount;
}
}