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
这个提交包含在:
@@ -3,39 +3,45 @@ package autodig.client.controller;
|
||||
import autodig.client.bridge.BaritoneBridge;
|
||||
import autodig.client.config.DigConfig;
|
||||
import autodig.client.config.MineTask;
|
||||
import autodig.client.miner.DebrisHandler;
|
||||
import autodig.client.miner.FishboneMiner;
|
||||
import autodig.client.miner.NetherMiner;
|
||||
import autodig.client.miner.OverworldMiner;
|
||||
import autodig.client.service.AutoEat;
|
||||
import autodig.client.service.DeathRecorder;
|
||||
import autodig.client.service.KillAura;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.Heightmap;
|
||||
|
||||
/**
|
||||
* 挖矿总控制器:把配置、Baritone 桥接、自动进食、杀戮光环、死亡记录和各任务
|
||||
* 专用挖矿器串起来,驱动一个简单的状态机。
|
||||
* 挖矿总控制器:把配置、Baritone 桥接、自动进食、杀戮光环、死亡记录、装备耐久
|
||||
* 保护和各任务专用挖矿器串起来,驱动一个简单的状态机。
|
||||
*/
|
||||
public final class DigController {
|
||||
|
||||
/** 内部状态机 */
|
||||
private enum State {
|
||||
IDLE, FISHBONE, CLIMB, DEBRIS, FLEE, RETURN_TO_DEATH
|
||||
IDLE, OVERWORLD, CLIMB, NETHER, FLEE, RETURN_TO_DEATH, GEAR_PROTECTION
|
||||
}
|
||||
|
||||
private final BaritoneBridge bridge = new BaritoneBridge();
|
||||
private final AutoEat autoEat = new AutoEat();
|
||||
private final KillAura killAura = new KillAura();
|
||||
private final DeathRecorder deathRecorder = new DeathRecorder();
|
||||
private final FishboneMiner fishbone = new FishboneMiner(this.bridge);
|
||||
private final DebrisHandler debris = new DebrisHandler(this.bridge);
|
||||
private final OverworldMiner overworld = new OverworldMiner(this.bridge);
|
||||
private final NetherMiner nether = new NetherMiner(this.bridge);
|
||||
|
||||
private State state = State.IDLE;
|
||||
private BlockPos resumeTarget;
|
||||
private long lastIssue;
|
||||
private boolean wasDead;
|
||||
private long lastWarn;
|
||||
/** 返回死亡点的截止时间(超时后直接续任务,避免不可达时死循环) */
|
||||
private long returnDeadline;
|
||||
/** 装备耐久保护触发前所处的状态,用于恢复后回到该状态 */
|
||||
private State preGearState;
|
||||
|
||||
/** 每帧驱动整个自动挖矿流程。 */
|
||||
public void tick(DigConfig config) {
|
||||
@@ -53,14 +59,45 @@ public final class DigController {
|
||||
// 死亡处理:死亡瞬间暂停任务,重生后按需回到死亡坐标
|
||||
this.deathRecorder.tick(config, this::onRespawn);
|
||||
if (player.isDead()) {
|
||||
this.wasDead = true;
|
||||
this.bridge.cancelAll();
|
||||
return;
|
||||
}
|
||||
this.wasDead = false;
|
||||
|
||||
// 保命优先:血线过低时向上撤离(不冒进挖矿)
|
||||
// 装备耐久保护(仅在挖矿任务进行中检查)
|
||||
if (config.task != MineTask.NONE && this.state != State.GEAR_PROTECTION) {
|
||||
if (this.isGearLowDurability(config, player)) {
|
||||
this.preGearState = this.state;
|
||||
this.bridge.cancelAll();
|
||||
// 下界模式:装备耐久低时直接返程回主世界
|
||||
if (config.task == MineTask.NETHER && config.netherAutoReturn) {
|
||||
this.nether.markGearBroken();
|
||||
this.setState(State.NETHER, "装备耐久过低,下界返程中");
|
||||
} else {
|
||||
this.setState(State.GEAR_PROTECTION, "装备耐久过低,暂停挖矿保装备");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 装备耐久保护恢复
|
||||
if (this.state == State.GEAR_PROTECTION) {
|
||||
if (!this.isGearLowDurability(config, player)) {
|
||||
this.setState(this.preGearState != null ? this.preGearState : State.IDLE,
|
||||
"装备耐久已恢复,继续挖矿");
|
||||
this.preGearState = null;
|
||||
} else {
|
||||
// 仍在装备保护中,等待玩家换装备
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 保命优先:血线过低时向上撤离(不冒进挖矿)。
|
||||
// cancelAll 只在刚进入 FLEE 时执行一次,避免每帧取消刚派发的寻路
|
||||
if (config.survivalFirst && this.healthPercent(player) < config.healthFleeThreshold) {
|
||||
if (this.state != State.FLEE) {
|
||||
this.bridge.cancelAll();
|
||||
}
|
||||
this.setState(State.FLEE, "血线过低,保命撤离中");
|
||||
this.fleeTick(config);
|
||||
return;
|
||||
}
|
||||
@@ -69,7 +106,7 @@ public final class DigController {
|
||||
if (config.task == MineTask.NONE) {
|
||||
if (this.state != State.IDLE) {
|
||||
this.bridge.cancelAll();
|
||||
this.state = State.IDLE;
|
||||
this.setState(State.IDLE, "任务已停止,进入空闲状态");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -80,36 +117,59 @@ public final class DigController {
|
||||
private void drive(DigConfig config, PlayerEntity player) {
|
||||
switch (this.state) {
|
||||
case IDLE -> {
|
||||
if (config.task == MineTask.DIAMOND) {
|
||||
this.fishbone.init(config);
|
||||
this.state = State.FISHBONE;
|
||||
} else if (config.task == MineTask.ANCIENT_DEBRIS) {
|
||||
this.debris.reset();
|
||||
this.state = State.DEBRIS;
|
||||
// Baritone 未就绪时无法挖矿,节流提示并停留在空闲,避免静默无响应
|
||||
if (!this.bridge.isAvailable()) {
|
||||
this.warnBaritoneUnavailable();
|
||||
break;
|
||||
}
|
||||
if (config.task == MineTask.OVERWORLD) {
|
||||
this.overworld.init(config);
|
||||
this.setState(State.OVERWORLD,
|
||||
"开始主世界鱼骨挖矿(Y=" + config.overworldLevel + ",巷道 " + config.tunnelLength + " 格)");
|
||||
} else if (config.task == MineTask.NETHER) {
|
||||
this.nether.reset();
|
||||
this.setState(State.NETHER, "开始下界挖矿(目标 " + config.netherTargetCount + " 个)");
|
||||
}
|
||||
}
|
||||
case FISHBONE -> {
|
||||
player.sendMessage(Text.literal("AutoDig: 主世界鱼骨挖矿进行中"), true);
|
||||
if (this.fishbone.tick(config)) {
|
||||
case OVERWORLD -> {
|
||||
if (this.overworld.tick(config)) {
|
||||
this.bridge.cancelAll();
|
||||
this.pathToSurface(player.getBlockPos());
|
||||
this.state = State.CLIMB;
|
||||
if (this.overworld.isFull()) {
|
||||
// 背包已满无法继续,直接结束任务,避免空转循环
|
||||
this.finish(config, "背包已满,任务已暂停(清空背包后重新开始)");
|
||||
} else {
|
||||
this.pathToSurface(player.getBlockPos());
|
||||
this.setState(State.CLIMB, "鱼骨巷道已挖完,正在返回地表");
|
||||
}
|
||||
}
|
||||
}
|
||||
case CLIMB -> {
|
||||
if (this.nearSurface(player)) {
|
||||
this.finish("鱼骨挖矿完成,已返回地表");
|
||||
this.finish(config, "主世界挖矿完成,已返回地表");
|
||||
} else {
|
||||
// 周期性重发寻路指令,避免 Baritone 中途失败后卡死在该状态
|
||||
this.pathToSurface(player.getBlockPos());
|
||||
}
|
||||
}
|
||||
case DEBRIS -> {
|
||||
player.sendMessage(Text.literal("AutoDig: 下界远古残骸挖矿进行中"), true);
|
||||
if (this.debris.tick(config)) {
|
||||
this.finish("远古残骸任务完成,已回到主世界");
|
||||
case NETHER -> {
|
||||
if (this.nether.tick(config)) {
|
||||
if (this.nether.isFull()) {
|
||||
this.finish(config, "背包已满,下界任务已暂停(清空背包后重新开始)");
|
||||
} else if (this.nether.isFled()) {
|
||||
this.finish(config, "血量过低触发返程,下界任务已完成(已回主世界)");
|
||||
} else if (this.nether.isGearBroken()) {
|
||||
this.finish(config, "装备耐久过低触发返程,下界任务已完成(已回主世界,请修理装备)");
|
||||
} else {
|
||||
this.finish(config, "下界任务完成,已回到主世界");
|
||||
}
|
||||
}
|
||||
}
|
||||
case RETURN_TO_DEATH -> this.returnToDeathTick(player, config);
|
||||
case FLEE -> {
|
||||
// FLEE 在 tick() 中单独处理,正常情况下不会进到这里
|
||||
// FLEE 在 tick() 中单独处理(血线恢复后自动续任务)
|
||||
}
|
||||
case GEAR_PROTECTION -> {
|
||||
// 装备耐久保护在 tick() 中单独处理
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,12 +180,19 @@ public final class DigController {
|
||||
if (deathPos != null) {
|
||||
this.bridge.cancelAll();
|
||||
this.resumeTarget = deathPos;
|
||||
this.state = State.RETURN_TO_DEATH;
|
||||
this.returnDeadline = System.currentTimeMillis() + 120_000;
|
||||
this.setState(State.RETURN_TO_DEATH, "已重生,正在返回死亡点继续任务");
|
||||
}
|
||||
}
|
||||
|
||||
private void returnToDeathTick(PlayerEntity player, DigConfig config) {
|
||||
if (this.resumeTarget == null) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
// 维度不一致(如下界死亡、主世界床上重生)或超时则放弃返回,直接续任务
|
||||
boolean dimOk = client.world != null
|
||||
&& client.world.getRegistryKey() == this.deathRecorder.lastDeathDim();
|
||||
if (this.resumeTarget == null || !dimOk
|
||||
|| System.currentTimeMillis() > this.returnDeadline) {
|
||||
this.resumeTarget = null;
|
||||
this.resumeTask(config);
|
||||
return;
|
||||
}
|
||||
@@ -138,22 +205,16 @@ public final class DigController {
|
||||
}
|
||||
}
|
||||
|
||||
/** 按当前任务与所在维度恢复挖矿。 */
|
||||
/** 按当前任务恢复挖矿(尽量复用已有巷道计划)。 */
|
||||
private void resumeTask(DigConfig config) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
boolean inNether = client.world != null && client.world.getRegistryKey() == net.minecraft.world.World.NETHER;
|
||||
if (config.task == MineTask.DIAMOND) {
|
||||
this.fishbone.init(config);
|
||||
this.state = State.FISHBONE;
|
||||
} else if (config.task == MineTask.ANCIENT_DEBRIS) {
|
||||
this.debris.reset();
|
||||
this.state = State.DEBRIS;
|
||||
// 若已在下界,让残留处理机直接从挖矿阶段继续
|
||||
if (inNether) {
|
||||
this.state = State.DEBRIS;
|
||||
}
|
||||
if (config.task == MineTask.OVERWORLD) {
|
||||
this.overworld.continueOrInit(config);
|
||||
this.setState(State.OVERWORLD, "继续主世界鱼骨挖矿");
|
||||
} else if (config.task == MineTask.NETHER) {
|
||||
this.nether.reset();
|
||||
this.setState(State.NETHER, "继续下界挖矿");
|
||||
} else {
|
||||
this.state = State.IDLE;
|
||||
this.setState(State.IDLE, "任务已结束");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,20 +225,52 @@ public final class DigController {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
this.bridge.cancelAll();
|
||||
this.pathToSurface(player.getBlockPos());
|
||||
if (this.healthPercent(player) >= config.healthFleeThreshold + 20) {
|
||||
this.resumeTask(config);
|
||||
}
|
||||
}
|
||||
|
||||
private void finish(String message) {
|
||||
/**
|
||||
* 结束任务:取消 Baritone、把配置任务重置为空闲(避免 IDLE 分支再次自动
|
||||
* 重开造成刷屏循环),并提示结果。
|
||||
*/
|
||||
private void finish(DigConfig config, String message) {
|
||||
this.bridge.cancelAll();
|
||||
config.task = MineTask.NONE;
|
||||
this.setState(State.IDLE, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换状态机状态,并在状态真正发生变化时向玩家发一条普通聊天提示
|
||||
* (非字幕 overlay)。仅变化时提示一次,避免每帧刷屏。
|
||||
*
|
||||
* @param next 目标状态
|
||||
* @param message 提示内容;为 null 或空串时不提示
|
||||
*/
|
||||
private void setState(State next, String message) {
|
||||
boolean changed = this.state != next;
|
||||
this.state = next;
|
||||
if (changed && message != null && !message.isEmpty()) {
|
||||
this.sendChat(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** 向玩家发送一条普通聊天提示(非字幕 overlay)。 */
|
||||
private void sendChat(String message) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.player != null) {
|
||||
client.player.sendMessage(Text.literal("AutoDig: " + message), true);
|
||||
client.player.sendMessage(Text.literal("AutoDig: " + message), false);
|
||||
}
|
||||
}
|
||||
|
||||
/** Baritone 未就绪时每 3 秒提示一次,避免刷屏,同时避免静默无响应。 */
|
||||
private void warnBaritoneUnavailable() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - this.lastWarn > 3000) {
|
||||
this.lastWarn = now;
|
||||
this.sendChat("内置 Baritone 未能就绪,无法开始挖矿(请尝试重启游戏;若你另外安装了 Baritone,请先移除,避免与本模组内置版本冲突)");
|
||||
}
|
||||
this.state = State.IDLE;
|
||||
}
|
||||
|
||||
private double healthPercent(PlayerEntity player) {
|
||||
@@ -223,13 +316,56 @@ public final class DigController {
|
||||
return Math.abs(player.getY() - (top + 1)) < 4.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查装备耐久是否过低。
|
||||
* 护甲:任意一件护甲耐久百分比低于阈值
|
||||
* 工具:手持工具耐久百分比低于阈值
|
||||
*/
|
||||
private boolean isGearLowDurability(DigConfig config, PlayerEntity player) {
|
||||
// 护甲耐久检查
|
||||
if (config.armorDurabilityProtection) {
|
||||
for (ItemStack armor : player.getInventory().armor) {
|
||||
if (armor.isEmpty() || !armor.isDamageable()) {
|
||||
continue;
|
||||
}
|
||||
int maxDmg = armor.getMaxDamage();
|
||||
int remaining = maxDmg - armor.getDamage();
|
||||
double percent = (double) remaining / maxDmg * 100.0;
|
||||
if (percent < config.armorDurabilityThreshold) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 手持工具耐久检查
|
||||
if (config.toolDurabilityProtection) {
|
||||
ItemStack mainHand = player.getMainHandStack();
|
||||
if (!mainHand.isEmpty() && mainHand.isDamageable()) {
|
||||
int maxDmg = mainHand.getMaxDamage();
|
||||
int remaining = maxDmg - mainHand.getDamage();
|
||||
double percent = (double) remaining / maxDmg * 100.0;
|
||||
if (percent < config.toolDurabilityThreshold) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return Baritone 桥接(供其他模块复用)。 */
|
||||
public BaritoneBridge bridge() {
|
||||
return this.bridge;
|
||||
}
|
||||
|
||||
/** 供 GUI/调试获取当前状态文本。 */
|
||||
/** 供 GUI/调试获取当前状态文本(中文描述)。 */
|
||||
public String statusText() {
|
||||
return this.state.name();
|
||||
return switch (this.state) {
|
||||
case IDLE -> "空闲";
|
||||
case OVERWORLD -> "主世界鱼骨挖矿中";
|
||||
case CLIMB -> "返回地表";
|
||||
case NETHER -> "下界挖矿中";
|
||||
case FLEE -> "保命撤离中";
|
||||
case RETURN_TO_DEATH -> "返回死亡点";
|
||||
case GEAR_PROTECTION -> "装备保护中";
|
||||
};
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户