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
这个提交包含在:
+12
-5
@@ -10,7 +10,9 @@ base {
|
|||||||
archivesName = project.archives_base_name
|
archivesName = project.archives_base_name
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用于把 Baritone 原样打包进模组 jar 的资源(不放默认依赖里,避免 Gradle 误解析)
|
// 独立配置,用于把完整的 Baritone 实现原样(不经过 Loom 重映射)复制进最终 jar。
|
||||||
|
// 注意:libs 下的这个文件虽然命名为 *-api-*,但它实际上是 Baritone 的完整实现
|
||||||
|
// (含混淆后的实现类、fabric.mod.json、Mixin 配置),并非只有接口。
|
||||||
configurations {
|
configurations {
|
||||||
nestedJar
|
nestedJar
|
||||||
}
|
}
|
||||||
@@ -24,10 +26,13 @@ dependencies {
|
|||||||
// Fabric API
|
// Fabric API
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||||
|
|
||||||
// Baritone API (local jar, compile only)
|
// Baritone 完整实现(本地 jar):
|
||||||
|
// - modImplementation 供编译期与开发环境(runClient)使用,Loom 会将其重映射到 yarn;
|
||||||
|
// - 生产 jar 中则通过下方 nestedJar 嵌入原始(intermediary 映射)文件,
|
||||||
|
// 由 Fabric Loader 在运行时自动作为嵌套模组加载,玩家无需另行安装 Baritone。
|
||||||
modImplementation files("libs/baritone-api-fabric-1.13.1.jar")
|
modImplementation files("libs/baritone-api-fabric-1.13.1.jar")
|
||||||
|
|
||||||
// 标记需要嵌套打包的 jar
|
// 标记需要原样嵌套打包的 jar(保持原始字节,不做重映射)
|
||||||
nestedJar files("libs/baritone-api-fabric-1.13.1.jar")
|
nestedJar files("libs/baritone-api-fabric-1.13.1.jar")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,8 +44,10 @@ processResources {
|
|||||||
expand "version": project.version
|
expand "version": project.version
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将 Baritone 嵌套打包进 META-INF/jars,匹配 fabric.mod.json 的 jars 声明,
|
// 将完整的 Baritone 实现原样嵌套打包进 META-INF/jars,
|
||||||
// 使玩家无需单独安装 baritone 依赖即可运行
|
// 与 fabric.mod.json 的 jars 声明对应。Fabric Loader 会在运行时把嵌套 jar
|
||||||
|
// 作为独立模组自动加载,因此玩家无需另行安装 Baritone。
|
||||||
|
// 注意:不要对嵌套副本做重映射,Loader 要求生产环境的模组保持 intermediary 映射。
|
||||||
from(configurations.nestedJar) {
|
from(configurations.nestedJar) {
|
||||||
into "META-INF/jars"
|
into "META-INF/jars"
|
||||||
}
|
}
|
||||||
|
|||||||
+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=1.0.0
|
mod_version=2.0.0
|
||||||
maven_group=autodig
|
maven_group=autodig
|
||||||
archives_base_name=autodig
|
archives_base_name=autodig
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import autodig.client.bridge.BaritoneBridge;
|
|||||||
import autodig.client.command.AutoDigCommands;
|
import autodig.client.command.AutoDigCommands;
|
||||||
import autodig.client.config.ConfigStore;
|
import autodig.client.config.ConfigStore;
|
||||||
import autodig.client.config.DigConfig;
|
import autodig.client.config.DigConfig;
|
||||||
|
import autodig.client.config.MineTask;
|
||||||
import autodig.client.controller.DigController;
|
import autodig.client.controller.DigController;
|
||||||
import autodig.client.gui.AutoDigGui;
|
import autodig.client.gui.AutoDigGui;
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
@@ -35,6 +36,8 @@ public final class AutoDigModClient implements ClientModInitializer {
|
|||||||
private DigConfig config;
|
private DigConfig config;
|
||||||
private boolean openGuiRequested;
|
private boolean openGuiRequested;
|
||||||
private KeyBinding guiKey;
|
private KeyBinding guiKey;
|
||||||
|
/** 上一次同步的任务,用于检测 controller 内部改动(如任务完成)并落盘 */
|
||||||
|
private MineTask lastTask;
|
||||||
|
|
||||||
private static final int KEY_R = 82;
|
private static final int KEY_R = 82;
|
||||||
private static final int KEY_LEFT_CTRL = 341;
|
private static final int KEY_LEFT_CTRL = 341;
|
||||||
@@ -44,15 +47,23 @@ public final class AutoDigModClient implements ClientModInitializer {
|
|||||||
public void onInitializeClient() {
|
public void onInitializeClient() {
|
||||||
instance = this;
|
instance = this;
|
||||||
this.config = this.configStore.load();
|
this.config = this.configStore.load();
|
||||||
|
this.lastTask = this.config.task;
|
||||||
this.guiKey = KeyBindingHelper.registerKeyBinding(new KeyBinding(
|
this.guiKey = KeyBindingHelper.registerKeyBinding(new KeyBinding(
|
||||||
"key.autodig.open", KEY_R, "category.autodig"));
|
"key.autodig.open", KEY_R, "category.autodig"));
|
||||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||||
// 同步模组配置到 Baritone(工具切换 / 避岩浆 / 避怪/保命)
|
// 同步模组配置到 Baritone(工具切换 / 避岩浆 / 避怪/保命)
|
||||||
this.bridge().applyConfig(this.config);
|
this.bridge().applyConfig(this.config);
|
||||||
this.controller.tick(this.config);
|
this.controller.tick(this.config);
|
||||||
|
// controller 可能因任务完成而把 task 置空,这里落盘,避免下次进游戏自动挖矿
|
||||||
|
if (this.config.task != this.lastTask) {
|
||||||
|
this.lastTask = this.config.task;
|
||||||
|
this.saveConfig();
|
||||||
|
}
|
||||||
while (this.guiKey.wasPressed()) {
|
while (this.guiKey.wasPressed()) {
|
||||||
if (this.isCtrlDown(client)) {
|
if (this.isCtrlDown(client)) {
|
||||||
this.openGui();
|
this.openGui();
|
||||||
|
} else {
|
||||||
|
this.sendToast("AutoDig: 请按住 Ctrl 再按此快捷键打开界面");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.openGuiRequested) {
|
if (this.openGuiRequested) {
|
||||||
|
|||||||
@@ -6,10 +6,8 @@ import baritone.api.IBaritone;
|
|||||||
import baritone.api.Settings;
|
import baritone.api.Settings;
|
||||||
import baritone.api.utils.BlockOptionalMeta;
|
import baritone.api.utils.BlockOptionalMeta;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.block.Blocks;
|
import net.minecraft.block.Blocks;
|
||||||
import net.minecraft.entity.Entity;
|
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,34 +15,41 @@ import net.minecraft.util.math.BlockPos;
|
|||||||
*
|
*
|
||||||
* <p>把模组对 Baritone 的所有调用集中在此类:同步用户配置(工具切换、避岩浆、
|
* <p>把模组对 Baritone 的所有调用集中在此类:同步用户配置(工具切换、避岩浆、
|
||||||
* 避怪、保命优先)、按名字/数量挖矿、前往指定坐标、前往最近传送门、
|
* 避怪、保命优先)、按名字/数量挖矿、前往指定坐标、前往最近传送门、
|
||||||
* 跟随并猎杀生物(杀戮光环之猎杀模式)、取消任务等。</p>
|
* 取消任务等。(杀戮光环由 KillAura 近战实现,不经 Baritone。)</p>
|
||||||
*
|
*
|
||||||
* <p>所有调用都做空安全保护,Baritone 未加载时相关操作会安全地静默失败。</p>
|
* <p>所有调用都做空安全保护,Baritone 未加载时相关操作会安全地静默失败。</p>
|
||||||
*/
|
*/
|
||||||
public final class BaritoneBridge {
|
public final class BaritoneBridge {
|
||||||
|
|
||||||
|
/** 上一次打印 gotoBlock 诊断日志的时间戳,用于节流 */
|
||||||
|
private long lastGotoLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把模组配置同步到 Baritone 全局设置。
|
* 把模组配置同步到 Baritone 全局设置。
|
||||||
*
|
*
|
||||||
* @param config 挖矿配置
|
* @param config 挖矿配置
|
||||||
*/
|
*/
|
||||||
public void applyConfig(DigConfig config) {
|
public void applyConfig(DigConfig config) {
|
||||||
Settings settings = BaritoneAPI.getSettings();
|
try {
|
||||||
settings.allowSprint.value = true;
|
Settings settings = BaritoneAPI.getSettings();
|
||||||
settings.allowBreak.value = true; // 挖矿必须允许挖方块
|
settings.allowSprint.value = true;
|
||||||
settings.allowPlace.value = true; // 允许搭路/垫脚
|
settings.allowBreak.value = true; // 挖矿必须允许挖方块
|
||||||
settings.allowParkour.value = true;
|
settings.allowPlace.value = true; // 允许搭路/垫脚
|
||||||
settings.allowParkourPlace.value = true;
|
settings.allowParkour.value = true;
|
||||||
settings.freeLook.value = true;
|
settings.allowParkourPlace.value = true;
|
||||||
// 自动切换最合适工具
|
settings.freeLook.value = true;
|
||||||
settings.autoTool.value = config.autoTool;
|
// 自动切换最合适工具
|
||||||
// 保命优先:让 Baritone 寻路时主动绕开敌对生物
|
settings.autoTool.value = config.autoTool;
|
||||||
settings.avoidance.value = config.survivalFirst;
|
// 保命优先:让 Baritone 寻路时主动绕开敌对生物
|
||||||
settings.mobAvoidanceRadius.value = 5;
|
settings.avoidance.value = config.survivalFirst;
|
||||||
// 优先绕开岩浆池:给岩浆(含流动岩浆所在方块)额外的寻路代价
|
settings.mobAvoidanceRadius.value = 5;
|
||||||
settings.blocksToAvoid.value = config.avoidLava
|
// 优先绕开岩浆池:给岩浆(含流动岩浆所在方块)额外的寻路代价
|
||||||
? List.of(Blocks.LAVA)
|
settings.blocksToAvoid.value = config.avoidLava
|
||||||
: List.of();
|
? List.of(Blocks.LAVA)
|
||||||
|
: List.of();
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// Baritone 未就绪时静默失败,避免抛出异常中断每帧逻辑
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,9 +70,28 @@ public final class BaritoneBridge {
|
|||||||
public void gotoBlock(BlockPos target) {
|
public void gotoBlock(BlockPos target) {
|
||||||
IBaritone baritone = this.primary();
|
IBaritone baritone = this.primary();
|
||||||
if (baritone == null || target == null) {
|
if (baritone == null || target == null) {
|
||||||
|
System.out.println("[AutoDig] gotoBlock 失败: baritone=" + (baritone != null) + " target=" + target);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
baritone.getCustomGoalProcess().setGoalAndPath(new baritone.api.pathing.goals.GoalBlock(target));
|
try {
|
||||||
|
boolean ctxOk = baritone.getPlayerContext() != null;
|
||||||
|
boolean worldOk = ctxOk && baritone.getPlayerContext().world() != null;
|
||||||
|
boolean playerOk = ctxOk && baritone.getPlayerContext().player() != null;
|
||||||
|
boolean wasPathing = baritone.getPathingBehavior().isPathing();
|
||||||
|
baritone.getCustomGoalProcess().setGoalAndPath(new baritone.api.pathing.goals.GoalBlock(target));
|
||||||
|
boolean nowPathing = baritone.getPathingBehavior().isPathing();
|
||||||
|
boolean goalActive = baritone.getCustomGoalProcess().isActive();
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now - this.lastGotoLog > 5000) {
|
||||||
|
this.lastGotoLog = now;
|
||||||
|
System.out.println("[AutoDig] gotoBlock target=" + target
|
||||||
|
+ " ctx=" + ctxOk + " world=" + worldOk + " player=" + playerOk
|
||||||
|
+ " wasPathing=" + wasPathing + " nowPathing=" + nowPathing
|
||||||
|
+ " goalActive=" + goalActive);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("[AutoDig] gotoBlock 异常: " + t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 前往离玩家最近的传送门方块(用于进出下界)。 */
|
/** 前往离玩家最近的传送门方块(用于进出下界)。 */
|
||||||
@@ -79,21 +103,6 @@ public final class BaritoneBridge {
|
|||||||
baritone.getGetToBlockProcess().getToBlock(new BlockOptionalMeta(Blocks.NETHER_PORTAL));
|
baritone.getGetToBlockProcess().getToBlock(new BlockOptionalMeta(Blocks.NETHER_PORTAL));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 是否正在采集一个收尾中的目标(传送门等)。 */
|
|
||||||
public boolean gettingToBlock() {
|
|
||||||
IBaritone baritone = this.primary();
|
|
||||||
return baritone != null && baritone.getGetToBlockProcess().isActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 跟随/猎杀所有满足条件的生物(杀戮光环之猎杀模式)。 */
|
|
||||||
public void follow(Predicate<Entity> filter) {
|
|
||||||
IBaritone baritone = this.primary();
|
|
||||||
if (baritone == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
baritone.getFollowProcess().follow(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 取消当前 Baritone 任务(挖矿/寻路/跟随)。 */
|
/** 取消当前 Baritone 任务(挖矿/寻路/跟随)。 */
|
||||||
public void cancelAll() {
|
public void cancelAll() {
|
||||||
IBaritone baritone = this.primary();
|
IBaritone baritone = this.primary();
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public final class AutoDigCommands {
|
|||||||
String name = StringArgumentType.getString(ctx, "task");
|
String name = StringArgumentType.getString(ctx, "task");
|
||||||
MineTask task = parseTask(name);
|
MineTask task = parseTask(name);
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
ctx.getSource().sendFeedback(Text.literal("未知任务: " + name + "(可用 diamond / debris)"));
|
ctx.getSource().sendFeedback(Text.literal("未知任务: " + name + "(可用 overworld / nether / none)"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
AutoDigModClient mod = AutoDigModClient.instance();
|
AutoDigModClient mod = AutoDigModClient.instance();
|
||||||
@@ -56,8 +56,8 @@ public final class AutoDigCommands {
|
|||||||
|
|
||||||
private static MineTask parseTask(String name) {
|
private static MineTask parseTask(String name) {
|
||||||
return switch (name.toLowerCase()) {
|
return switch (name.toLowerCase()) {
|
||||||
case "diamond", "钻石" -> MineTask.DIAMOND;
|
case "overworld", "主世界", "ow" -> MineTask.OVERWORLD;
|
||||||
case "debris", "ancient_debris", "远古残骸" -> MineTask.ANCIENT_DEBRIS;
|
case "nether", "下界" -> MineTask.NETHER;
|
||||||
case "none", "stop", "空闲" -> MineTask.NONE;
|
case "none", "stop", "空闲" -> MineTask.NONE;
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import java.nio.file.OpenOption;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.attribute.FileAttribute;
|
import java.nio.file.attribute.FileAttribute;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置文件读写器。
|
* 配置文件读写器。
|
||||||
@@ -19,6 +21,8 @@ import net.fabricmc.loader.api.FabricLoader;
|
|||||||
*/
|
*/
|
||||||
public final class ConfigStore {
|
public final class ConfigStore {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("AutoDig");
|
||||||
|
|
||||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
|
||||||
public DigConfig load() {
|
public DigConfig load() {
|
||||||
@@ -32,8 +36,9 @@ public final class ConfigStore {
|
|||||||
if (loaded == null) {
|
if (loaded == null) {
|
||||||
return new DigConfig();
|
return new DigConfig();
|
||||||
}
|
}
|
||||||
return loaded;
|
return sanitize(loaded);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
LOGGER.warn("AutoDig: 读取配置失败,已回退到默认配置: {}", e.getMessage());
|
||||||
return new DigConfig();
|
return new DigConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,12 +50,44 @@ public final class ConfigStore {
|
|||||||
Files.createDirectories(path.getParent(), new FileAttribute[0]);
|
Files.createDirectories(path.getParent(), new FileAttribute[0]);
|
||||||
}
|
}
|
||||||
Files.writeString(path, GSON.toJson(config), StandardCharsets.UTF_8, new OpenOption[0]);
|
Files.writeString(path, GSON.toJson(config), StandardCharsets.UTF_8, new OpenOption[0]);
|
||||||
} catch (IOException iOException) {
|
} catch (IOException e) {
|
||||||
// 保存失败时静默忽略
|
LOGGER.warn("AutoDig: 保存配置失败: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path path() {
|
private Path path() {
|
||||||
return FabricLoader.getInstance().getConfigDir().resolve("autodig.json");
|
return FabricLoader.getInstance().getConfigDir().resolve("autodig.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把磁盘上的配置夹回合法范围,避免手改 JSON 或将来字段重命名导致运行时异常。
|
||||||
|
* Gson 对缺失字段会保留字段初始化值,对显式 null 会写入 null,所以这里两种都要处理。
|
||||||
|
*/
|
||||||
|
private static DigConfig sanitize(DigConfig c) {
|
||||||
|
if (c.task == null) {
|
||||||
|
c.task = MineTask.NONE;
|
||||||
|
}
|
||||||
|
c.eatHungerThreshold = clamp(c.eatHungerThreshold, 1, 20);
|
||||||
|
c.healthFleeThreshold = clamp(c.healthFleeThreshold, 5, 100);
|
||||||
|
c.armorDurabilityThreshold = clamp(c.armorDurabilityThreshold, 1, 100);
|
||||||
|
c.toolDurabilityThreshold = clamp(c.toolDurabilityThreshold, 1, 100);
|
||||||
|
c.netherFleeHealthThreshold = clamp(c.netherFleeHealthThreshold, 0, 100);
|
||||||
|
if (Double.isNaN(c.killRange) || c.killRange < 2.0 || c.killRange > 8.0) {
|
||||||
|
c.killRange = 4.0;
|
||||||
|
}
|
||||||
|
c.overworldLevel = clamp(c.overworldLevel, -64, 320);
|
||||||
|
c.tunnelLength = clamp(c.tunnelLength, 4, 512);
|
||||||
|
c.netherTargetCount = clamp(c.netherTargetCount, -1, 512);
|
||||||
|
if (c.overworldOres == null || c.overworldOres.isBlank()) {
|
||||||
|
c.overworldOres = "diamond_ore,deepslate_diamond_ore";
|
||||||
|
}
|
||||||
|
if (c.netherOres == null || c.netherOres.isBlank()) {
|
||||||
|
c.netherOres = "ancient_debris";
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int clamp(int v, int min, int max) {
|
||||||
|
return Math.max(min, Math.min(max, v));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
package autodig.client.config;
|
package autodig.client.config;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 挖矿任务的用户可配置项(可序列化为 JSON,由 GUI 直接读写)。
|
* 挖矿任务的用户可配置项(可序列化为 JSON,由 GUI 直接读写)。
|
||||||
*/
|
*/
|
||||||
public class DigConfig {
|
public class DigConfig {
|
||||||
|
|
||||||
/** 当前选中的挖矿任务 */
|
/** 当前选中的挖矿任务(默认空闲,需通过 GUI/命令手动开始,避免进游戏自动挖矿) */
|
||||||
public MineTask task = MineTask.DIAMOND;
|
public MineTask task = MineTask.NONE;
|
||||||
|
|
||||||
// ---- 通用能力开关 ----
|
// ---- 通用能力开关 ----
|
||||||
/** 自动切换最合适的工具(钻石稿/铁镐等) */
|
/** 自动切换最合适的工具(钻石稿/铁镐等) */
|
||||||
@@ -22,6 +25,16 @@ public class DigConfig {
|
|||||||
/** 生命值(半心百分比,0-100)低于该值时触发保命撤离 */
|
/** 生命值(半心百分比,0-100)低于该值时触发保命撤离 */
|
||||||
public int healthFleeThreshold = 40;
|
public int healthFleeThreshold = 40;
|
||||||
|
|
||||||
|
// ---- 装备保护 ----
|
||||||
|
/** 护甲耐久保护:任意护甲件耐久低于该百分比时暂停挖矿(主世界/下界均生效) */
|
||||||
|
public boolean armorDurabilityProtection = true;
|
||||||
|
/** 护甲耐久阈值(百分比 0-100),低于此值停止挖矿 */
|
||||||
|
public int armorDurabilityThreshold = 20;
|
||||||
|
/** 工具耐久保护:手持工具耐久低于该百分比时暂停挖矿 */
|
||||||
|
public boolean toolDurabilityProtection = true;
|
||||||
|
/** 工具耐久阈值(百分比 0-100),低于此值停止挖矿 */
|
||||||
|
public int toolDurabilityThreshold = 15;
|
||||||
|
|
||||||
// ---- 岩浆/障碍 ----
|
// ---- 岩浆/障碍 ----
|
||||||
/** 优先绕开岩浆池(给岩浆方块额外寻路代价) */
|
/** 优先绕开岩浆池(给岩浆方块额外寻路代价) */
|
||||||
public boolean avoidLava = true;
|
public boolean avoidLava = true;
|
||||||
@@ -33,25 +46,51 @@ public class DigConfig {
|
|||||||
public boolean killAuraSafe = true;
|
public boolean killAuraSafe = true;
|
||||||
/** 杀戮光环攻击范围(方块) */
|
/** 杀戮光环攻击范围(方块) */
|
||||||
public double killRange = 4.0;
|
public double killRange = 4.0;
|
||||||
|
/** 下界模式下启用杀戮光环协同(下界怪物多,建议开启) */
|
||||||
|
public boolean killAuraInNether = true;
|
||||||
|
|
||||||
// ---- 钻石(主世界)参数 ----
|
// ---- 主世界模式参数 ----
|
||||||
/** 鱼骨巷道瞄准的 Y 层(钻石一般在 -60 附近) */
|
/** 鱼骨巷道瞄准的 Y 层(钻石一般在 -60 附近) */
|
||||||
public int diamondLevel = -58;
|
public int overworldLevel = -58;
|
||||||
/** 鱼骨主干巷道长度(方块) */
|
/** 鱼骨主干巷道长度(方块) */
|
||||||
public int tunnelLength = 60;
|
public int tunnelLength = 60;
|
||||||
|
/** 主世界要挖的矿物方块 ID 列表(逗号分隔,如 "diamond_ore,deepslate_diamond_ore") */
|
||||||
|
public String overworldOres = "diamond_ore,deepslate_diamond_ore";
|
||||||
|
|
||||||
// ---- 远古残骸参数 ----
|
// ---- 下界模式参数 ----
|
||||||
/** 需要挖多少个远古残骸后视为完成 */
|
/** 下界要挖的矿物方块 ID 列表(逗号分隔,如 "ancient_debris,nether_gold_ore") */
|
||||||
public int debrisCount = 4;
|
public String netherOres = "ancient_debris";
|
||||||
/** 挖够数量后自动回到主世界 */
|
/** 挖够数量后自动回主世界(-1 表示无限挖直到背包满/血量低/装备耐久低) */
|
||||||
public boolean debrisReturnHome = true;
|
public int netherTargetCount = 4;
|
||||||
/** 是否开启下界的自动猎杀/杀戮光环协同 */
|
/** 下界模式:挖够/背包满/血量低/装备耐久低时自动回主世界 */
|
||||||
public boolean killAuraInNether = true;
|
public boolean netherAutoReturn = true;
|
||||||
|
/** 下界模式:血量低于该百分比时触发返程(0-100,0 关闭) */
|
||||||
|
public int netherFleeHealthThreshold = 30;
|
||||||
|
|
||||||
// ---- 死亡重生 ----
|
// ---- 死亡重生 ----
|
||||||
/** 死亡重生后自动回到死亡地点继续任务 */
|
/** 死亡重生后自动回到死亡地点继续任务 */
|
||||||
public boolean respawnReturn = true;
|
public boolean respawnReturn = true;
|
||||||
|
|
||||||
|
/** 获取主世界矿物列表 */
|
||||||
|
public List<String> getOverworldOreList() {
|
||||||
|
return parseOreList(this.overworldOres);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取下界矿物列表 */
|
||||||
|
public List<String> getNetherOreList() {
|
||||||
|
return parseOreList(this.netherOres);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> parseOreList(String str) {
|
||||||
|
if (str == null || str.isBlank()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return Arrays.stream(str.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
public DigConfig copy() {
|
public DigConfig copy() {
|
||||||
DigConfig c = new DigConfig();
|
DigConfig c = new DigConfig();
|
||||||
c.task = this.task;
|
c.task = this.task;
|
||||||
@@ -60,15 +99,22 @@ public class DigConfig {
|
|||||||
c.eatHungerThreshold = this.eatHungerThreshold;
|
c.eatHungerThreshold = this.eatHungerThreshold;
|
||||||
c.survivalFirst = this.survivalFirst;
|
c.survivalFirst = this.survivalFirst;
|
||||||
c.healthFleeThreshold = this.healthFleeThreshold;
|
c.healthFleeThreshold = this.healthFleeThreshold;
|
||||||
|
c.armorDurabilityProtection = this.armorDurabilityProtection;
|
||||||
|
c.armorDurabilityThreshold = this.armorDurabilityThreshold;
|
||||||
|
c.toolDurabilityProtection = this.toolDurabilityProtection;
|
||||||
|
c.toolDurabilityThreshold = this.toolDurabilityThreshold;
|
||||||
c.avoidLava = this.avoidLava;
|
c.avoidLava = this.avoidLava;
|
||||||
c.killAura = this.killAura;
|
c.killAura = this.killAura;
|
||||||
c.killAuraSafe = this.killAuraSafe;
|
c.killAuraSafe = this.killAuraSafe;
|
||||||
c.killRange = this.killRange;
|
c.killRange = this.killRange;
|
||||||
c.diamondLevel = this.diamondLevel;
|
|
||||||
c.tunnelLength = this.tunnelLength;
|
|
||||||
c.debrisCount = this.debrisCount;
|
|
||||||
c.debrisReturnHome = this.debrisReturnHome;
|
|
||||||
c.killAuraInNether = this.killAuraInNether;
|
c.killAuraInNether = this.killAuraInNether;
|
||||||
|
c.overworldLevel = this.overworldLevel;
|
||||||
|
c.tunnelLength = this.tunnelLength;
|
||||||
|
c.overworldOres = this.overworldOres;
|
||||||
|
c.netherOres = this.netherOres;
|
||||||
|
c.netherTargetCount = this.netherTargetCount;
|
||||||
|
c.netherAutoReturn = this.netherAutoReturn;
|
||||||
|
c.netherFleeHealthThreshold = this.netherFleeHealthThreshold;
|
||||||
c.respawnReturn = this.respawnReturn;
|
c.respawnReturn = this.respawnReturn;
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ package autodig.client.config;
|
|||||||
/**
|
/**
|
||||||
* 挖矿任务类型。
|
* 挖矿任务类型。
|
||||||
*
|
*
|
||||||
* <p>不同任务对应不同的自动挖矿策略:主世界钻石走鱼骨巷道 + Baritone 矿脉探测,
|
* <p>主世界模式:在主世界按鱼骨巷道挖掘可配置的矿物(默认钻石/深层钻石),有死亡不掉落保护。
|
||||||
* 远古残骸需要先进传送门下界、再矿脉探测、挖完/死亡后自动回主世界。</p>
|
* 下界模式:自动进传送门进入下界挖掘可配置的矿物(默认远古残骸),无死亡不掉落,需惜命,
|
||||||
|
* 提供返程功能(挖够/背包满/血量低/装备耐久低时自动回主世界),并珍惜装备耐久。</p>
|
||||||
*/
|
*/
|
||||||
public enum MineTask {
|
public enum MineTask {
|
||||||
/** 空闲:不执行任何挖矿任务 */
|
/** 空闲:不执行任何挖矿任务 */
|
||||||
NONE("空闲"),
|
NONE("空闲"),
|
||||||
/** 主世界挖钻石(含深层钻石矿石) */
|
/** 主世界模式:鱼骨巷道挖掘可配置矿物 */
|
||||||
DIAMOND("钻石"),
|
OVERWORLD("主世界"),
|
||||||
/** 下界挖远古残骸(自动进出传送门、死亡重生返回坐标) */
|
/** 下界模式:进传送门挖掘可配置矿物,支持自动返程与惜命/护装备逻辑 */
|
||||||
ANCIENT_DEBRIS("远古残骸");
|
NETHER("下界");
|
||||||
|
|
||||||
private final String label;
|
private final String label;
|
||||||
|
|
||||||
|
|||||||
@@ -3,39 +3,45 @@ package autodig.client.controller;
|
|||||||
import autodig.client.bridge.BaritoneBridge;
|
import autodig.client.bridge.BaritoneBridge;
|
||||||
import autodig.client.config.DigConfig;
|
import autodig.client.config.DigConfig;
|
||||||
import autodig.client.config.MineTask;
|
import autodig.client.config.MineTask;
|
||||||
import autodig.client.miner.DebrisHandler;
|
import autodig.client.miner.NetherMiner;
|
||||||
import autodig.client.miner.FishboneMiner;
|
import autodig.client.miner.OverworldMiner;
|
||||||
import autodig.client.service.AutoEat;
|
import autodig.client.service.AutoEat;
|
||||||
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.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.world.Heightmap;
|
import net.minecraft.world.Heightmap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 挖矿总控制器:把配置、Baritone 桥接、自动进食、杀戮光环、死亡记录和各任务
|
* 挖矿总控制器:把配置、Baritone 桥接、自动进食、杀戮光环、死亡记录、装备耐久
|
||||||
* 专用挖矿器串起来,驱动一个简单的状态机。
|
* 保护和各任务专用挖矿器串起来,驱动一个简单的状态机。
|
||||||
*/
|
*/
|
||||||
public final class DigController {
|
public final class DigController {
|
||||||
|
|
||||||
/** 内部状态机 */
|
/** 内部状态机 */
|
||||||
private enum State {
|
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 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 DeathRecorder deathRecorder = new DeathRecorder();
|
private final DeathRecorder deathRecorder = new DeathRecorder();
|
||||||
private final FishboneMiner fishbone = new FishboneMiner(this.bridge);
|
private final OverworldMiner overworld = new OverworldMiner(this.bridge);
|
||||||
private final DebrisHandler debris = new DebrisHandler(this.bridge);
|
private final NetherMiner nether = new NetherMiner(this.bridge);
|
||||||
|
|
||||||
private State state = State.IDLE;
|
private State state = State.IDLE;
|
||||||
private BlockPos resumeTarget;
|
private BlockPos resumeTarget;
|
||||||
private long lastIssue;
|
private long lastIssue;
|
||||||
private boolean wasDead;
|
private long lastWarn;
|
||||||
|
/** 返回死亡点的截止时间(超时后直接续任务,避免不可达时死循环) */
|
||||||
|
private long returnDeadline;
|
||||||
|
/** 装备耐久保护触发前所处的状态,用于恢复后回到该状态 */
|
||||||
|
private State preGearState;
|
||||||
|
|
||||||
/** 每帧驱动整个自动挖矿流程。 */
|
/** 每帧驱动整个自动挖矿流程。 */
|
||||||
public void tick(DigConfig config) {
|
public void tick(DigConfig config) {
|
||||||
@@ -53,14 +59,45 @@ public final class DigController {
|
|||||||
// 死亡处理:死亡瞬间暂停任务,重生后按需回到死亡坐标
|
// 死亡处理:死亡瞬间暂停任务,重生后按需回到死亡坐标
|
||||||
this.deathRecorder.tick(config, this::onRespawn);
|
this.deathRecorder.tick(config, this::onRespawn);
|
||||||
if (player.isDead()) {
|
if (player.isDead()) {
|
||||||
this.wasDead = true;
|
|
||||||
this.bridge.cancelAll();
|
this.bridge.cancelAll();
|
||||||
return;
|
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 (config.survivalFirst && this.healthPercent(player) < config.healthFleeThreshold) {
|
||||||
|
if (this.state != State.FLEE) {
|
||||||
|
this.bridge.cancelAll();
|
||||||
|
}
|
||||||
|
this.setState(State.FLEE, "血线过低,保命撤离中");
|
||||||
this.fleeTick(config);
|
this.fleeTick(config);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -69,7 +106,7 @@ public final class DigController {
|
|||||||
if (config.task == MineTask.NONE) {
|
if (config.task == MineTask.NONE) {
|
||||||
if (this.state != State.IDLE) {
|
if (this.state != State.IDLE) {
|
||||||
this.bridge.cancelAll();
|
this.bridge.cancelAll();
|
||||||
this.state = State.IDLE;
|
this.setState(State.IDLE, "任务已停止,进入空闲状态");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -80,36 +117,59 @@ public final class DigController {
|
|||||||
private void drive(DigConfig config, PlayerEntity player) {
|
private void drive(DigConfig config, PlayerEntity player) {
|
||||||
switch (this.state) {
|
switch (this.state) {
|
||||||
case IDLE -> {
|
case IDLE -> {
|
||||||
if (config.task == MineTask.DIAMOND) {
|
// Baritone 未就绪时无法挖矿,节流提示并停留在空闲,避免静默无响应
|
||||||
this.fishbone.init(config);
|
if (!this.bridge.isAvailable()) {
|
||||||
this.state = State.FISHBONE;
|
this.warnBaritoneUnavailable();
|
||||||
} else if (config.task == MineTask.ANCIENT_DEBRIS) {
|
break;
|
||||||
this.debris.reset();
|
}
|
||||||
this.state = State.DEBRIS;
|
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 -> {
|
case OVERWORLD -> {
|
||||||
player.sendMessage(Text.literal("AutoDig: 主世界鱼骨挖矿进行中"), true);
|
if (this.overworld.tick(config)) {
|
||||||
if (this.fishbone.tick(config)) {
|
|
||||||
this.bridge.cancelAll();
|
this.bridge.cancelAll();
|
||||||
this.pathToSurface(player.getBlockPos());
|
if (this.overworld.isFull()) {
|
||||||
this.state = State.CLIMB;
|
// 背包已满无法继续,直接结束任务,避免空转循环
|
||||||
|
this.finish(config, "背包已满,任务已暂停(清空背包后重新开始)");
|
||||||
|
} else {
|
||||||
|
this.pathToSurface(player.getBlockPos());
|
||||||
|
this.setState(State.CLIMB, "鱼骨巷道已挖完,正在返回地表");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case CLIMB -> {
|
case CLIMB -> {
|
||||||
if (this.nearSurface(player)) {
|
if (this.nearSurface(player)) {
|
||||||
this.finish("鱼骨挖矿完成,已返回地表");
|
this.finish(config, "主世界挖矿完成,已返回地表");
|
||||||
|
} else {
|
||||||
|
// 周期性重发寻路指令,避免 Baritone 中途失败后卡死在该状态
|
||||||
|
this.pathToSurface(player.getBlockPos());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case DEBRIS -> {
|
case NETHER -> {
|
||||||
player.sendMessage(Text.literal("AutoDig: 下界远古残骸挖矿进行中"), true);
|
if (this.nether.tick(config)) {
|
||||||
if (this.debris.tick(config)) {
|
if (this.nether.isFull()) {
|
||||||
this.finish("远古残骸任务完成,已回到主世界");
|
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 RETURN_TO_DEATH -> this.returnToDeathTick(player, config);
|
||||||
case FLEE -> {
|
case FLEE -> {
|
||||||
// FLEE 在 tick() 中单独处理,正常情况下不会进到这里
|
// FLEE 在 tick() 中单独处理(血线恢复后自动续任务)
|
||||||
|
}
|
||||||
|
case GEAR_PROTECTION -> {
|
||||||
|
// 装备耐久保护在 tick() 中单独处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,12 +180,19 @@ public final class DigController {
|
|||||||
if (deathPos != null) {
|
if (deathPos != null) {
|
||||||
this.bridge.cancelAll();
|
this.bridge.cancelAll();
|
||||||
this.resumeTarget = deathPos;
|
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) {
|
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);
|
this.resumeTask(config);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -138,22 +205,16 @@ public final class DigController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 按当前任务与所在维度恢复挖矿。 */
|
/** 按当前任务恢复挖矿(尽量复用已有巷道计划)。 */
|
||||||
private void resumeTask(DigConfig config) {
|
private void resumeTask(DigConfig config) {
|
||||||
MinecraftClient client = MinecraftClient.getInstance();
|
if (config.task == MineTask.OVERWORLD) {
|
||||||
boolean inNether = client.world != null && client.world.getRegistryKey() == net.minecraft.world.World.NETHER;
|
this.overworld.continueOrInit(config);
|
||||||
if (config.task == MineTask.DIAMOND) {
|
this.setState(State.OVERWORLD, "继续主世界鱼骨挖矿");
|
||||||
this.fishbone.init(config);
|
} else if (config.task == MineTask.NETHER) {
|
||||||
this.state = State.FISHBONE;
|
this.nether.reset();
|
||||||
} else if (config.task == MineTask.ANCIENT_DEBRIS) {
|
this.setState(State.NETHER, "继续下界挖矿");
|
||||||
this.debris.reset();
|
|
||||||
this.state = State.DEBRIS;
|
|
||||||
// 若已在下界,让残留处理机直接从挖矿阶段继续
|
|
||||||
if (inNether) {
|
|
||||||
this.state = State.DEBRIS;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.state = State.IDLE;
|
this.setState(State.IDLE, "任务已结束");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,20 +225,52 @@ public final class DigController {
|
|||||||
if (player == null) {
|
if (player == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.bridge.cancelAll();
|
|
||||||
this.pathToSurface(player.getBlockPos());
|
this.pathToSurface(player.getBlockPos());
|
||||||
if (this.healthPercent(player) >= config.healthFleeThreshold + 20) {
|
if (this.healthPercent(player) >= config.healthFleeThreshold + 20) {
|
||||||
this.resumeTask(config);
|
this.resumeTask(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void finish(String message) {
|
/**
|
||||||
|
* 结束任务:取消 Baritone、把配置任务重置为空闲(避免 IDLE 分支再次自动
|
||||||
|
* 重开造成刷屏循环),并提示结果。
|
||||||
|
*/
|
||||||
|
private void finish(DigConfig config, String message) {
|
||||||
this.bridge.cancelAll();
|
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();
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
if (client.player != null) {
|
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) {
|
private double healthPercent(PlayerEntity player) {
|
||||||
@@ -223,13 +316,56 @@ public final class DigController {
|
|||||||
return Math.abs(player.getY() - (top + 1)) < 4.0;
|
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 桥接(供其他模块复用)。 */
|
/** @return Baritone 桥接(供其他模块复用)。 */
|
||||||
public BaritoneBridge bridge() {
|
public BaritoneBridge bridge() {
|
||||||
return this.bridge;
|
return this.bridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 供 GUI/调试获取当前状态文本。 */
|
/** 供 GUI/调试获取当前状态文本(中文描述)。 */
|
||||||
public String statusText() {
|
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 -> "装备保护中";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,27 +14,39 @@ import net.minecraft.text.Text;
|
|||||||
/**
|
/**
|
||||||
* AutoDig 游戏内 GUI。
|
* AutoDig 游戏内 GUI。
|
||||||
*
|
*
|
||||||
* <p>用于选择挖什么(钻石 / 远古残骸),并快捷设置:保命优先、优先绕开岩浆、
|
* <p>用于选择挖矿模式(主世界 / 下界),并快捷设置:保命优先、优先绕开岩浆、
|
||||||
* 自动切换工具、自动进食、杀戮光环、重生后回死亡点、挖完回主世界,
|
* 自动切换工具、自动进食、杀戮光环(含下界协同)、重生后回死亡点、挖完回主世界,
|
||||||
* 以及钻石层数 / 巷道长度 / 残骸数量等参数。界面中的改动在"开始/停止/关闭"时
|
* 装备耐久保护、矿物选择,以及鱼骨层数 / 巷道长度 / 目标数量等参数。
|
||||||
* 同步到 mod 全局配置并落盘。</p>
|
* 界面中的改动在"开始/停止"或常规关闭(含 ESC)时同步到 mod 全局配置并落盘;
|
||||||
|
* "仅关闭"则丢弃未应用的改动。</p>
|
||||||
*/
|
*/
|
||||||
public final class AutoDigGui extends Screen {
|
public final class AutoDigGui extends Screen {
|
||||||
|
|
||||||
private final AutoDigModClient mod;
|
private final AutoDigModClient mod;
|
||||||
private final DigConfig config;
|
private final DigConfig config;
|
||||||
private final DigController controller;
|
private final DigController controller;
|
||||||
|
/** 关闭界面时是否把改动写回 mod 配置("仅关闭"会置 false) */
|
||||||
|
private boolean applyOnClose = true;
|
||||||
|
|
||||||
// 动态数值按钮(点击 +/- 后刷新显示)
|
// 动态数值按钮(点击 +/- 后刷新显示)
|
||||||
private ButtonWidget levelBtn;
|
private ButtonWidget levelBtn;
|
||||||
private ButtonWidget tunnelBtn;
|
private ButtonWidget tunnelBtn;
|
||||||
private ButtonWidget debrisBtn;
|
private ButtonWidget countBtn;
|
||||||
|
private ButtonWidget armorDurBtn;
|
||||||
|
private ButtonWidget toolDurBtn;
|
||||||
|
private ButtonWidget fleeHBtn;
|
||||||
|
// 矿物输入框状态
|
||||||
|
private String overworldOresDraft;
|
||||||
|
private String netherOresDraft;
|
||||||
|
private int oreInputFocused = -1; // 0 = overworld, 1 = nether
|
||||||
|
|
||||||
public AutoDigGui(AutoDigModClient mod) {
|
public AutoDigGui(AutoDigModClient mod) {
|
||||||
super(Text.literal("AutoDig"));
|
super(Text.literal("AutoDig"));
|
||||||
this.mod = mod;
|
this.mod = mod;
|
||||||
this.config = mod.config().copy();
|
this.config = mod.config().copy();
|
||||||
this.controller = mod.controller();
|
this.controller = mod.controller();
|
||||||
|
this.overworldOresDraft = this.config.overworldOres;
|
||||||
|
this.netherOresDraft = this.config.netherOres;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -45,8 +57,8 @@ public final class AutoDigGui extends Screen {
|
|||||||
int y = 34;
|
int y = 34;
|
||||||
// ===== 任务选择 =====
|
// ===== 任务选择 =====
|
||||||
this.taskButton("空闲", MineTask.NONE, x, y);
|
this.taskButton("空闲", MineTask.NONE, x, y);
|
||||||
this.taskButton("钻石(鱼骨)", MineTask.DIAMOND, x + 110, y);
|
this.taskButton("主世界", MineTask.OVERWORLD, x + 110, y);
|
||||||
this.taskButton("远古残骸", MineTask.ANCIENT_DEBRIS, x + 220, y);
|
this.taskButton("下界", MineTask.NETHER, x + 220, y);
|
||||||
|
|
||||||
// ===== 开关设置 =====
|
// ===== 开关设置 =====
|
||||||
y += 30;
|
y += 30;
|
||||||
@@ -59,29 +71,102 @@ public final class AutoDigGui extends Screen {
|
|||||||
this.check("杀戮光环", colX, y, () -> this.config.killAura, v -> this.config.killAura = v);
|
this.check("杀戮光环", colX, y, () -> this.config.killAura, v -> this.config.killAura = v);
|
||||||
this.check("重生后回死亡点", colX + 150, y, () -> this.config.respawnReturn, v -> this.config.respawnReturn = v);
|
this.check("重生后回死亡点", colX + 150, y, () -> this.config.respawnReturn, v -> this.config.respawnReturn = v);
|
||||||
y += 24;
|
y += 24;
|
||||||
this.check("挖完回主世界", colX, y, () -> this.config.debrisReturnHome, v -> this.config.debrisReturnHome = v);
|
this.check("下界挖完回主世界", colX, y, () -> this.config.netherAutoReturn, v -> this.config.netherAutoReturn = v);
|
||||||
this.check("避难时绝杀不冒进", colX + 150, y, () -> this.config.killAuraSafe, v -> this.config.killAuraSafe = v);
|
this.check("避难时绝杀不冒进", colX + 150, y, () -> this.config.killAuraSafe, v -> this.config.killAuraSafe = v);
|
||||||
|
y += 24;
|
||||||
|
this.check("下界协同杀戮光环", colX, y, () -> this.config.killAuraInNether, v -> this.config.killAuraInNether = v);
|
||||||
|
this.check("护甲耐久保护", colX + 150, y, () -> this.config.armorDurabilityProtection, v -> this.config.armorDurabilityProtection = v);
|
||||||
|
y += 24;
|
||||||
|
this.check("工具耐久保护", colX, y, () -> this.config.toolDurabilityProtection, v -> this.config.toolDurabilityProtection = v);
|
||||||
|
|
||||||
// ===== 数值参数 =====
|
// ===== 数值参数 =====
|
||||||
y += 32;
|
y += 32;
|
||||||
this.levelBtn = this.numberRow("钻石层(Y)", colX, y, () -> this.config.diamondLevel, v -> this.config.diamondLevel = v);
|
this.levelBtn = this.numberRow("挖掘层(Y)", colX, y, () -> this.config.overworldLevel, v -> this.config.overworldLevel = v, -64, 320);
|
||||||
y += 24;
|
y += 24;
|
||||||
this.tunnelBtn = this.numberRow("巷道长度", colX, y, () -> this.config.tunnelLength, v -> this.config.tunnelLength = v);
|
this.tunnelBtn = this.numberRow("巷道长度", colX, y, () -> this.config.tunnelLength, v -> this.config.tunnelLength = v, 4, 512);
|
||||||
y += 24;
|
y += 24;
|
||||||
this.debrisBtn = this.numberRow("残骸数量", colX, y, () -> this.config.debrisCount, v -> this.config.debrisCount = v);
|
this.countBtn = this.numberRow("下界目标数量", colX, y, () -> this.config.netherTargetCount, v -> this.config.netherTargetCount = v, -1, 512);
|
||||||
|
y += 24;
|
||||||
|
this.armorDurBtn = this.numberRow("护甲耐久阈值%", colX, y, () -> this.config.armorDurabilityThreshold, v -> this.config.armorDurabilityThreshold = v, 1, 100);
|
||||||
|
y += 24;
|
||||||
|
this.toolDurBtn = this.numberRow("工具耐久阈值%", colX, y, () -> this.config.toolDurabilityThreshold, v -> this.config.toolDurabilityThreshold = v, 1, 100);
|
||||||
|
y += 24;
|
||||||
|
this.fleeHBtn = this.numberRow("下界返程血量%", colX, y, () -> this.config.netherFleeHealthThreshold, v -> this.config.netherFleeHealthThreshold = v, 0, 100);
|
||||||
|
|
||||||
|
// ===== 矿物选择 =====
|
||||||
|
y += 32;
|
||||||
|
this.addDrawableChild(ButtonWidget.builder(Text.literal("主世界矿物: " + this.overworldOresDraft), b -> {
|
||||||
|
// 循环切换预设矿物组合
|
||||||
|
this.overworldOresDraft = nextOverworldOres(this.overworldOresDraft);
|
||||||
|
this.refreshOreButtons();
|
||||||
|
}).dimensions(colX, y, 300, 20).build());
|
||||||
|
y += 24;
|
||||||
|
this.addDrawableChild(ButtonWidget.builder(Text.literal("下界矿物: " + this.netherOresDraft), b -> {
|
||||||
|
// 循环切换预设矿物组合
|
||||||
|
this.netherOresDraft = nextNetherOres(this.netherOresDraft);
|
||||||
|
this.refreshOreButtons();
|
||||||
|
}).dimensions(colX, y, 300, 20).build());
|
||||||
|
|
||||||
// ===== 底部操作按钮 =====
|
// ===== 底部操作按钮 =====
|
||||||
y += 40;
|
y += 32;
|
||||||
ButtonWidget start = this.textButton("开始 (保存并运行)", b -> this.start(), x, y, 140);
|
ButtonWidget start = this.textButton("开始 (保存并运行)", b -> this.start(), x, y, 140);
|
||||||
ButtonWidget stop = this.textButton("停止", b -> this.stop(), x + 146, y, 90);
|
ButtonWidget stop = this.textButton("停止", b -> this.stop(), x + 146, y, 90);
|
||||||
ButtonWidget saveClose = this.textButton("仅关闭", b -> this.close(), x + 242, y, 80);
|
ButtonWidget saveClose = this.textButton("仅关闭", b -> this.closeOnly(), x + 242, y, 80);
|
||||||
this.addDrawableChild(start);
|
this.addDrawableChild(start);
|
||||||
this.addDrawableChild(stop);
|
this.addDrawableChild(stop);
|
||||||
this.addDrawableChild(saveClose);
|
this.addDrawableChild(saveClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 主世界矿物预设循环 */
|
||||||
|
private static String[] OVERWORLD_ORE_PRESETS = {
|
||||||
|
"diamond_ore,deepslate_diamond_ore",
|
||||||
|
"diamond_ore,deepslate_diamond_ore,iron_ore,deepslate_iron_ore,coal_ore,deepslate_coal_ore",
|
||||||
|
"diamond_ore,deepslate_diamond_ore,emerald_ore,deepslate_emerald_ore",
|
||||||
|
"gold_ore,deepslate_gold_ore",
|
||||||
|
"lapis_ore,deepslate_lapis_ore",
|
||||||
|
"redstone_ore,deepslate_redstone_ore",
|
||||||
|
"copper_ore,deepslate_copper_ore",
|
||||||
|
"iron_ore,deepslate_iron_ore",
|
||||||
|
"coal_ore,deepslate_coal_ore"
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 下界矿物预设循环 */
|
||||||
|
private static String[] NETHER_ORE_PRESETS = {
|
||||||
|
"ancient_debris",
|
||||||
|
"ancient_debris,nether_gold_ore",
|
||||||
|
"nether_gold_ore",
|
||||||
|
"nether_quartz_ore",
|
||||||
|
"ancient_debris,nether_gold_ore,nether_quartz_ore"
|
||||||
|
};
|
||||||
|
|
||||||
|
private String nextOverworldOres(String current) {
|
||||||
|
for (int i = 0; i < OVERWORLD_ORE_PRESETS.length; i++) {
|
||||||
|
if (OVERWORLD_ORE_PRESETS[i].equals(current)) {
|
||||||
|
return OVERWORLD_ORE_PRESETS[(i + 1) % OVERWORLD_ORE_PRESETS.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OVERWORLD_ORE_PRESETS[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private String nextNetherOres(String current) {
|
||||||
|
for (int i = 0; i < NETHER_ORE_PRESETS.length; i++) {
|
||||||
|
if (NETHER_ORE_PRESETS[i].equals(current)) {
|
||||||
|
return NETHER_ORE_PRESETS[(i + 1) % NETHER_ORE_PRESETS.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NETHER_ORE_PRESETS[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshOreButtons() {
|
||||||
|
// 按钮在 init() 中创建,文本更新需要重新 init
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
private ButtonWidget taskButton(String label, MineTask task, int x, int y) {
|
private ButtonWidget taskButton(String label, MineTask task, int x, int y) {
|
||||||
ButtonWidget b = this.textButton(label, button -> {
|
// 选中的任务用 [ ] 包裹,形成高亮提示
|
||||||
|
boolean selected = this.config.task == task;
|
||||||
|
String text = selected ? "[" + label + "]" : label;
|
||||||
|
ButtonWidget b = this.textButton(text, button -> {
|
||||||
this.config.task = task;
|
this.config.task = task;
|
||||||
this.init(); // 刷新选中高亮
|
this.init(); // 刷新选中高亮
|
||||||
}, x, y, 104);
|
}, x, y, 104);
|
||||||
@@ -96,9 +181,9 @@ public final class AutoDigGui extends Screen {
|
|||||||
this.addDrawableChild(box);
|
this.addDrawableChild(box);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ButtonWidget numberRow(String label, int x, int y, java.util.function.IntSupplier getter, Consumer<Integer> setter) {
|
private ButtonWidget numberRow(String label, int x, int y, java.util.function.IntSupplier getter, Consumer<Integer> setter, int min, int max) {
|
||||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("-"), b -> {
|
this.addDrawableChild(ButtonWidget.builder(Text.literal("-"), b -> {
|
||||||
setter.accept(getter.getAsInt() - 1);
|
setter.accept(this.clamp(getter.getAsInt() - 1, min, max));
|
||||||
this.refreshNumberButtons();
|
this.refreshNumberButtons();
|
||||||
}).dimensions(x, y, 20, 20).build());
|
}).dimensions(x, y, 20, 20).build());
|
||||||
int valueX = x + 24;
|
int valueX = x + 24;
|
||||||
@@ -108,22 +193,39 @@ public final class AutoDigGui extends Screen {
|
|||||||
value.active = false; // 纯展示
|
value.active = false; // 纯展示
|
||||||
this.addDrawableChild(value);
|
this.addDrawableChild(value);
|
||||||
this.addDrawableChild(ButtonWidget.builder(Text.literal("+"), b -> {
|
this.addDrawableChild(ButtonWidget.builder(Text.literal("+"), b -> {
|
||||||
setter.accept(getter.getAsInt() + 1);
|
setter.accept(this.clamp(getter.getAsInt() + 1, min, max));
|
||||||
this.refreshNumberButtons();
|
this.refreshNumberButtons();
|
||||||
}).dimensions(valueX + valueW + 4, y, 20, 20).build());
|
}).dimensions(valueX + valueW + 4, y, 20, 20).build());
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int clamp(int v, int min, int max) {
|
||||||
|
return Math.max(min, Math.min(max, v));
|
||||||
|
}
|
||||||
|
|
||||||
private void refreshNumberButtons() {
|
private void refreshNumberButtons() {
|
||||||
this.levelBtn.setMessage(Text.literal("钻石层(Y) : " + this.config.diamondLevel));
|
this.levelBtn.setMessage(Text.literal("挖掘层(Y) : " + this.config.overworldLevel));
|
||||||
this.tunnelBtn.setMessage(Text.literal("巷道长度 : " + this.config.tunnelLength));
|
this.tunnelBtn.setMessage(Text.literal("巷道长度 : " + this.config.tunnelLength));
|
||||||
this.debrisBtn.setMessage(Text.literal("残骸数量 : " + this.config.debrisCount));
|
this.countBtn.setMessage(Text.literal("下界目标数量 : " + this.config.netherTargetCount));
|
||||||
|
this.armorDurBtn.setMessage(Text.literal("护甲耐久阈值% : " + this.config.armorDurabilityThreshold));
|
||||||
|
this.toolDurBtn.setMessage(Text.literal("工具耐久阈值% : " + this.config.toolDurabilityThreshold));
|
||||||
|
this.fleeHBtn.setMessage(Text.literal("下界返程血量% : " + this.config.netherFleeHealthThreshold));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ButtonWidget textButton(String label, ButtonWidget.PressAction action, int x, int y, int w) {
|
private ButtonWidget textButton(String label, ButtonWidget.PressAction action, int x, int y, int w) {
|
||||||
return ButtonWidget.builder(Text.literal(label), action).dimensions(x, y, w, 20).build();
|
return ButtonWidget.builder(Text.literal(label), action).dimensions(x, y, w, 20).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUI 打开时不允许游戏暂停:否则客户端 tick 不执行,{@link DigController}
|
||||||
|
* 每帧驱动逻辑停摆,玩家会停在原地不挖矿。这里返回 false,让挖矿在 GUI
|
||||||
|
* 打开状态下也能持续进行。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean shouldPause() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||||
super.render(context, mouseX, mouseY, delta);
|
super.render(context, mouseX, mouseY, delta);
|
||||||
@@ -143,32 +245,52 @@ public final class AutoDigGui extends Screen {
|
|||||||
target.autoTool = this.config.autoTool;
|
target.autoTool = this.config.autoTool;
|
||||||
target.killAura = this.config.killAura;
|
target.killAura = this.config.killAura;
|
||||||
target.killAuraSafe = this.config.killAuraSafe;
|
target.killAuraSafe = this.config.killAuraSafe;
|
||||||
|
target.killAuraInNether = this.config.killAuraInNether;
|
||||||
target.respawnReturn = this.config.respawnReturn;
|
target.respawnReturn = this.config.respawnReturn;
|
||||||
target.debrisReturnHome = this.config.debrisReturnHome;
|
target.netherAutoReturn = this.config.netherAutoReturn;
|
||||||
target.diamondLevel = this.config.diamondLevel;
|
target.overworldLevel = this.config.overworldLevel;
|
||||||
target.tunnelLength = this.config.tunnelLength;
|
target.tunnelLength = this.config.tunnelLength;
|
||||||
target.debrisCount = this.config.debrisCount;
|
target.overworldOres = this.overworldOresDraft;
|
||||||
|
target.netherOres = this.netherOresDraft;
|
||||||
|
target.netherTargetCount = this.config.netherTargetCount;
|
||||||
|
target.netherFleeHealthThreshold = this.config.netherFleeHealthThreshold;
|
||||||
|
target.armorDurabilityProtection = this.config.armorDurabilityProtection;
|
||||||
|
target.armorDurabilityThreshold = this.config.armorDurabilityThreshold;
|
||||||
|
target.toolDurabilityProtection = this.config.toolDurabilityProtection;
|
||||||
|
target.toolDurabilityThreshold = this.config.toolDurabilityThreshold;
|
||||||
this.mod.saveConfig();
|
this.mod.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void start() {
|
private void start() {
|
||||||
|
if (this.config.task == MineTask.NONE) {
|
||||||
|
this.mod.sendToast("AutoDig: 请先选择一个挖矿任务(主世界 / 下界)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.applyToMod();
|
this.applyToMod();
|
||||||
this.mod.sendToast("AutoDig: 已开始 " + this.config.task.label() + " 任务");
|
this.mod.sendToast("AutoDig: 已开始 " + this.config.task.label() + " 任务");
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stop() {
|
private void stop() {
|
||||||
|
// 先把本地副本的任务置空再写回,避免 close() 二次应用时把旧任务写回去
|
||||||
|
this.config.task = MineTask.NONE;
|
||||||
this.applyToMod();
|
this.applyToMod();
|
||||||
this.mod.config().task = MineTask.NONE;
|
|
||||||
this.mod.saveConfig();
|
|
||||||
this.controller.bridge().cancelAll();
|
this.controller.bridge().cancelAll();
|
||||||
this.mod.sendToast("AutoDig: 已停止");
|
this.mod.sendToast("AutoDig: 已停止");
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 仅关闭:丢弃未应用的改动。 */
|
||||||
|
private void closeOnly() {
|
||||||
|
this.applyOnClose = false;
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
this.applyToMod();
|
if (this.applyOnClose) {
|
||||||
|
this.applyToMod();
|
||||||
|
}
|
||||||
super.close();
|
super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package autodig.client.miner;
|
|
||||||
|
|
||||||
import autodig.client.bridge.BaritoneBridge;
|
|
||||||
import autodig.client.config.DigConfig;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
|
||||||
import net.minecraft.registry.RegistryKey;
|
|
||||||
import net.minecraft.world.World;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 远古残骸自动挖矿流转(下界)。
|
|
||||||
*
|
|
||||||
* <p>流程:主世界 -> 走进最近传送门 -> 进入下界 -> 挖远古残骸 ->
|
|
||||||
* (背包满/挖够)-> 找下界传送门走回主世界。死亡重生的返回逻辑由控制器统一处理,
|
|
||||||
* 本类每次 tick 依据当前所在维度自适应地执行对应阶段。</p>
|
|
||||||
*/
|
|
||||||
public final class DebrisHandler {
|
|
||||||
|
|
||||||
private final BaritoneBridge bridge;
|
|
||||||
|
|
||||||
private enum Stage {
|
|
||||||
/** 在下界从传送门前往主世界(或已在主世界即完成) */
|
|
||||||
GO_COMMIT,
|
|
||||||
/** 在前一个维度前往传送门 */
|
|
||||||
GO_PORTAL,
|
|
||||||
/** 挖远古残骸 */
|
|
||||||
MINE,
|
|
||||||
/** 返回主世界 */
|
|
||||||
RETURN_PORTAL
|
|
||||||
}
|
|
||||||
|
|
||||||
private Stage stage = Stage.GO_COMMIT;
|
|
||||||
private boolean done;
|
|
||||||
/** 派发节流 */
|
|
||||||
private long lastIssue;
|
|
||||||
|
|
||||||
public DebrisHandler(BaritoneBridge bridge) {
|
|
||||||
this.bridge = bridge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 任务结束或需要切换任务时归还控制权。 */
|
|
||||||
public void reset() {
|
|
||||||
this.stage = Stage.GO_COMMIT;
|
|
||||||
this.done = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 每帧推进下界挖矿流转。
|
|
||||||
*
|
|
||||||
* @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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
} else if (this.inventoryFull(player) || this.bridge.isActive() == false) {
|
|
||||||
// 背包满或 Baritone 空闲即为本段结束;若期望回程则前往传送门
|
|
||||||
if (config.debrisReturnHome) {
|
|
||||||
this.stage = Stage.RETURN_PORTAL;
|
|
||||||
} else {
|
|
||||||
this.stage = Stage.GO_COMMIT; // 停在下界挖(不返回)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.issue(() -> this.bridge.mineByName(config.debrisCount, "ancient_debris"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case RETURN_PORTAL -> {
|
|
||||||
if (inOverworld) {
|
|
||||||
this.done = true; // 已回到主世界,全流程完成
|
|
||||||
} else {
|
|
||||||
this.issue(() -> this.bridge.toNetherPortal());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.done;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDone() {
|
|
||||||
return this.done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+73
-18
@@ -9,7 +9,7 @@ import net.minecraft.entity.player.PlayerEntity;
|
|||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 鱼骨巷道挖矿器(主世界钻石)。
|
* 主世界鱼骨巷道挖矿器。
|
||||||
*
|
*
|
||||||
* <p>在指定的 Y 层,沿玩家朝向挖一条"主干隧道",并在主干上每隔
|
* <p>在指定的 Y 层,沿玩家朝向挖一条"主干隧道",并在主干上每隔
|
||||||
* {@code branchGap} 格向两侧各开一条侧支巷道,形成鱼骨状分布。
|
* {@code branchGap} 格向两侧各开一条侧支巷道,形成鱼骨状分布。
|
||||||
@@ -19,7 +19,7 @@ import net.minecraft.util.math.BlockPos;
|
|||||||
*
|
*
|
||||||
* <p>遇到背包已满时提前结束,把控制权交还控制器(用于返回地表)。</p>
|
* <p>遇到背包已满时提前结束,把控制权交还控制器(用于返回地表)。</p>
|
||||||
*/
|
*/
|
||||||
public final class FishboneMiner {
|
public final class OverworldMiner {
|
||||||
|
|
||||||
private final BaritoneBridge bridge;
|
private final BaritoneBridge bridge;
|
||||||
|
|
||||||
@@ -29,10 +29,14 @@ public final class FishboneMiner {
|
|||||||
private int step;
|
private int step;
|
||||||
/** 是否已完成 */
|
/** 是否已完成 */
|
||||||
private boolean done;
|
private boolean done;
|
||||||
|
/** 完成原因是否为「背包已满」 */
|
||||||
|
private boolean full;
|
||||||
/** 上一次朝向目标派发寻路的时间戳,用于失败重试节流 */
|
/** 上一次朝向目标派发寻路的时间戳,用于失败重试节流 */
|
||||||
private long lastIssue;
|
private long lastIssue;
|
||||||
|
/** 上一次打印诊断日志的时间戳,用于节流 */
|
||||||
|
private long lastLog;
|
||||||
|
|
||||||
public FishboneMiner(BaritoneBridge bridge) {
|
public OverworldMiner(BaritoneBridge bridge) {
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,17 +51,19 @@ public final class FishboneMiner {
|
|||||||
if (player == null) {
|
if (player == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.yLevel = config.diamondLevel;
|
this.yLevel = config.overworldLevel;
|
||||||
BlockPos stop = player.getBlockPos();
|
BlockPos stop = player.getBlockPos();
|
||||||
this.start = new BlockPos(stop.getX(), this.yLevel, stop.getZ());
|
this.start = new BlockPos(stop.getX(), this.yLevel, stop.getZ());
|
||||||
int branchLen = 6;
|
int branchLen = 6;
|
||||||
int branchGap = 8;
|
int branchGap = 8;
|
||||||
int axis = Math.abs(player.getMovementDirection().getOffsetX()) > Math.abs(player.getMovementDirection().getOffsetZ()) ? 0 : 1;
|
// 用玩家水平朝向(而非移动方向)确定挖矿方向,站着不动时也稳定
|
||||||
int sign = axis == 0 ? player.getMovementDirection().getOffsetX() : player.getMovementDirection().getOffsetZ();
|
net.minecraft.util.math.Direction facing = player.getHorizontalFacing();
|
||||||
sign = sign == 0 ? 1 : sign;
|
int axis = facing.getAxis() == net.minecraft.util.math.Direction.Axis.X ? 0 : 1;
|
||||||
|
int sign = axis == 0 ? facing.getOffsetX() : facing.getOffsetZ();
|
||||||
this.plan = this.buildPlan(this.start, this.yLevel, config.tunnelLength, branchLen, branchGap, axis, sign);
|
this.plan = this.buildPlan(this.start, this.yLevel, config.tunnelLength, branchLen, branchGap, axis, sign);
|
||||||
this.step = 0;
|
this.step = 0;
|
||||||
this.done = false;
|
this.done = false;
|
||||||
|
this.full = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -70,13 +76,15 @@ public final class FishboneMiner {
|
|||||||
for (int t = 0; t <= trunkLen; t++) {
|
for (int t = 0; t <= trunkLen; t++) {
|
||||||
BlockPos trunk = base.add(dx * t, 0, dz * t);
|
BlockPos trunk = base.add(dx * t, 0, dz * t);
|
||||||
plan.add(trunk);
|
plan.add(trunk);
|
||||||
// 每 branchGap 格开一对侧支(去程+回程)
|
// 每 branchGap 格向两侧各开一条侧支(去程+回程)
|
||||||
if (t > 0 && t % branchGap == 0) {
|
if (t > 0 && t % branchGap == 0) {
|
||||||
for (int b = 1; b <= branchLen; b++) {
|
for (int side = -1; side <= 1; side += 2) {
|
||||||
plan.add(trunk.add(sx * b, 0, sz * b));
|
for (int b = 1; b <= branchLen; b++) {
|
||||||
}
|
plan.add(trunk.add(sx * b * side, 0, sz * b * side));
|
||||||
for (int b = branchLen - 1; b >= 1; b--) {
|
}
|
||||||
plan.add(trunk.add(sx * b, 0, sz * b));
|
for (int b = branchLen - 1; b >= 1; b--) {
|
||||||
|
plan.add(trunk.add(sx * b * side, 0, sz * b * side));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,14 +107,24 @@ public final class FishboneMiner {
|
|||||||
}
|
}
|
||||||
if (this.inventoryFull(player)) {
|
if (this.inventoryFull(player)) {
|
||||||
this.done = true;
|
this.done = true;
|
||||||
|
this.full = true;
|
||||||
|
System.out.println("[AutoDig] overworld 结束:背包已满");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.step >= this.plan.size()) {
|
if (this.step >= this.plan.size()) {
|
||||||
this.done = true;
|
this.done = true;
|
||||||
|
System.out.println("[AutoDig] overworld 结束:计划完成 step=" + this.step + " size=" + this.plan.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
BlockPos target = this.plan.get(this.step);
|
BlockPos target = this.plan.get(this.step);
|
||||||
double dist = this.distanceSq(player, target);
|
double dist = this.distanceSq(player, target);
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now - this.lastLog > 5000) {
|
||||||
|
this.lastLog = now;
|
||||||
|
System.out.println("[AutoDig] overworld: step=" + this.step + "/" + this.plan.size()
|
||||||
|
+ " player=" + player.getBlockPos() + " target=" + target
|
||||||
|
+ " dist=" + String.format("%.1f", dist) + " active=" + this.bridge.isActive());
|
||||||
|
}
|
||||||
if (dist <= 2.0) {
|
if (dist <= 2.0) {
|
||||||
// 已到达当前目标,推进到下一个
|
// 已到达当前目标,推进到下一个
|
||||||
this.step++;
|
this.step++;
|
||||||
@@ -114,7 +132,6 @@ public final class FishboneMiner {
|
|||||||
}
|
}
|
||||||
// 距离太长或 Baritone 没在干活时重新派发
|
// 距离太长或 Baritone 没在干活时重新派发
|
||||||
if (dist > 4.0 || !this.bridge.isActive()) {
|
if (dist > 4.0 || !this.bridge.isActive()) {
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - this.lastIssue > 250) {
|
if (now - this.lastIssue > 250) {
|
||||||
this.bridge.gotoBlock(target);
|
this.bridge.gotoBlock(target);
|
||||||
this.lastIssue = now;
|
this.lastIssue = now;
|
||||||
@@ -145,9 +162,47 @@ public final class FishboneMiner {
|
|||||||
return this.done;
|
return this.done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重新允许继续(死亡重生/保命恢复后复用)。 */
|
/** @return 完成原因是否为「背包已满」 */
|
||||||
public void abort() {
|
public boolean isFull() {
|
||||||
this.done = true;
|
return this.full;
|
||||||
this.plan = new ArrayList<>();
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复挖掘(死亡重生/保命恢复后复用):已有未完成计划时复用原计划,
|
||||||
|
* 把指针回退到离玩家最近的目标点;否则按当前位置重新建立计划。
|
||||||
|
*
|
||||||
|
* @param config 挖矿配置
|
||||||
|
*/
|
||||||
|
public void continueOrInit(DigConfig config) {
|
||||||
|
MinecraftClient client = MinecraftClient.getInstance();
|
||||||
|
PlayerEntity player = client.player;
|
||||||
|
if (this.plan.isEmpty() || this.done || player == null) {
|
||||||
|
this.init(config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int from = Math.max(0, this.step - 8);
|
||||||
|
int to = Math.min(this.plan.size() - 1, this.step + 8);
|
||||||
|
int best = this.step;
|
||||||
|
double bestDist = Double.MAX_VALUE;
|
||||||
|
for (int i = from; i <= to; i++) {
|
||||||
|
double d = this.distanceSq(player, this.plan.get(i));
|
||||||
|
if (d < bestDist) {
|
||||||
|
bestDist = d;
|
||||||
|
best = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.step = best;
|
||||||
|
this.done = false;
|
||||||
|
this.full = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 派发矿脉挖掘指令(用于在鱼骨巷道推进过程中遇到目标矿物时挖掉)。
|
||||||
|
* 由控制器在合适时机调用。
|
||||||
|
*/
|
||||||
|
public void mineOres(DigConfig config) {
|
||||||
|
if (!config.getOverworldOreList().isEmpty()) {
|
||||||
|
this.bridge.mineByName(-1, config.getOverworldOreList().toArray(String[]::new));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,13 +10,14 @@ import net.minecraft.util.Hand;
|
|||||||
/**
|
/**
|
||||||
* 自动进食服务。
|
* 自动进食服务。
|
||||||
*
|
*
|
||||||
* <p>饥饿度低于阈值时,自动从快捷栏挑选一件食物并持续"使用手指"进食。
|
* <p>饥饿度低于阈值时,自动从快捷栏/主背包挑选一件食物并持续"使用手指"进食。
|
||||||
|
* 食物在主背包时,先将其与当前快捷栏槽位交换,下一帧再吃。
|
||||||
* 已在使用物品(正在咀嚼)时不会重复触发,避免打断进食节奏。</p>
|
* 已在使用物品(正在咀嚼)时不会重复触发,避免打断进食节奏。</p>
|
||||||
*/
|
*/
|
||||||
public final class AutoEat {
|
public final class AutoEat {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 每帧调用。饥饿不够时在快捷栏里找食物并吃掉。
|
* 每帧调用。饥饿不够时在快捷栏/主背包里找食物并吃掉。
|
||||||
*
|
*
|
||||||
* @param config 挖矿配置
|
* @param config 挖矿配置
|
||||||
*/
|
*/
|
||||||
@@ -35,12 +36,17 @@ public final class AutoEat {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PlayerInventory inv = player.getInventory();
|
PlayerInventory inv = player.getInventory();
|
||||||
// 优先在快捷栏找食物
|
// 快捷栏 + 主背包共 36 格找食物
|
||||||
for (int slot = 0; slot < 9; slot++) {
|
for (int slot = 0; slot < 36; slot++) {
|
||||||
ItemStack stack = inv.getStack(slot);
|
ItemStack stack = inv.getStack(slot);
|
||||||
if (stack.isEmpty() || stack.getUseAction() != UseAction.EAT) {
|
if (stack.isEmpty() || stack.getUseAction() != UseAction.EAT) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (slot >= 9) {
|
||||||
|
// 主背包的食物先换到当前选中的快捷栏槽位,下一帧再吃
|
||||||
|
inv.swapSlotWithHotbar(slot);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 先切到该槽位,下一帧再触发进食
|
// 先切到该槽位,下一帧再触发进食
|
||||||
if (inv.selectedSlot != slot) {
|
if (inv.selectedSlot != slot) {
|
||||||
inv.selectedSlot = slot;
|
inv.selectedSlot = slot;
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package autodig.client.service;
|
|||||||
|
|
||||||
import autodig.client.config.DigConfig;
|
import autodig.client.config.DigConfig;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.registry.RegistryKey;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 死亡记录服务。
|
* 死亡记录服务。
|
||||||
*
|
*
|
||||||
* <p>在玩家死亡的那一刻记录其坐标;重生(isDead 由真转假)后,
|
* <p>在玩家死亡的那一刻记录其坐标与所在维度;重生(isDead 由真转假)后,
|
||||||
* 若开启了"重生返回",通过回调通知控制器派 Baritone 回到死亡地点继续任务。</p>
|
* 若开启了"重生返回",通过回调通知控制器派 Baritone 回到死亡地点继续任务。</p>
|
||||||
*/
|
*/
|
||||||
public final class DeathRecorder {
|
public final class DeathRecorder {
|
||||||
@@ -15,6 +17,8 @@ public final class DeathRecorder {
|
|||||||
private boolean wasDead;
|
private boolean wasDead;
|
||||||
/** 最近一次死亡的坐标 */
|
/** 最近一次死亡的坐标 */
|
||||||
private BlockPos deathPos;
|
private BlockPos deathPos;
|
||||||
|
/** 最近一次死亡时所在的维度 */
|
||||||
|
private RegistryKey<World> deathDim;
|
||||||
private boolean pendingReturn;
|
private boolean pendingReturn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,8 +34,9 @@ public final class DeathRecorder {
|
|||||||
}
|
}
|
||||||
boolean dead = client.player.isDead();
|
boolean dead = client.player.isDead();
|
||||||
if (dead && !this.wasDead) {
|
if (dead && !this.wasDead) {
|
||||||
// 刚死亡:记录死亡坐标(死亡界面时玩家仍停在该格)
|
// 刚死亡:记录死亡坐标与维度(死亡界面时玩家仍停在该格)
|
||||||
this.deathPos = client.player.getBlockPos();
|
this.deathPos = client.player.getBlockPos();
|
||||||
|
this.deathDim = client.world.getRegistryKey();
|
||||||
this.pendingReturn = config.respawnReturn;
|
this.pendingReturn = config.respawnReturn;
|
||||||
this.wasDead = true;
|
this.wasDead = true;
|
||||||
return;
|
return;
|
||||||
@@ -56,9 +61,8 @@ public final class DeathRecorder {
|
|||||||
return this.deathPos;
|
return this.deathPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 清除死亡记录。 */
|
/** @return 最近一次死亡时所在的维度,或 null */
|
||||||
public void clear() {
|
public RegistryKey<World> lastDeathDim() {
|
||||||
this.deathPos = null;
|
return this.deathDim;
|
||||||
this.pendingReturn = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,11 @@ import net.minecraft.client.MinecraftClient;
|
|||||||
import net.minecraft.entity.Entity;
|
import net.minecraft.entity.Entity;
|
||||||
import net.minecraft.entity.LivingEntity;
|
import net.minecraft.entity.LivingEntity;
|
||||||
import net.minecraft.entity.mob.Monster;
|
import net.minecraft.entity.mob.Monster;
|
||||||
|
import net.minecraft.util.Hand;
|
||||||
import net.minecraft.util.math.Box;
|
import net.minecraft.util.math.Box;
|
||||||
import net.minecraft.util.math.Vec3d;
|
import net.minecraft.util.math.Vec3d;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 杀戮光环服务。
|
* 杀戮光环服务。
|
||||||
@@ -33,6 +36,10 @@ public final class KillAura {
|
|||||||
if (!config.killAura) {
|
if (!config.killAura) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 下界协同开关:未开启时在下界不主动出击
|
||||||
|
if (!config.killAuraInNether && client.world.getRegistryKey() == World.NETHER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
net.minecraft.entity.player.PlayerEntity player = client.player;
|
net.minecraft.entity.player.PlayerEntity player = client.player;
|
||||||
if (player.isDead()) {
|
if (player.isDead()) {
|
||||||
return;
|
return;
|
||||||
@@ -60,7 +67,9 @@ public final class KillAura {
|
|||||||
// 转向目标
|
// 转向目标
|
||||||
this.faceEntity(player, target);
|
this.faceEntity(player, target);
|
||||||
client.interactionManager.attackEntity(player, target);
|
client.interactionManager.attackEntity(player, target);
|
||||||
this.attackCooldownTicks = 4; // 约 4 tick 一刀
|
player.swingHand(Hand.MAIN_HAND); // 视觉摆动
|
||||||
|
// 3-6 tick 抖动,避免固定间隔被反作弊识别
|
||||||
|
this.attackCooldownTicks = 3 + ThreadLocalRandom.current().nextInt(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 在玩家周围范围内找最近的敌对生物。 */
|
/** 在玩家周围范围内找最近的敌对生物。 */
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"id": "autodig",
|
"id": "autodig",
|
||||||
"version": "${version}",
|
"version": "${version}",
|
||||||
"name": "AutoDig",
|
"name": "AutoDig",
|
||||||
"description": "基于 Baritone API 的自动挖矿模组:可选钻石/远古残骸,支持鱼骨挖矿、自动切换工具、自动进食、杀戮光环、保命优先、优先绕开岩浆、进出传送门与死亡重生回点。",
|
"description": "自动挖矿模组(内置 Baritone 1.13.1,无需另行安装):主世界模式与下界模式,均可自定义目标矿物。主世界鱼骨巷道挖矿,下界自动进出传送门挖矿并支持返程。支持自动切换工具、自动进食、杀戮光环、保命优先、优先绕开岩浆、装备耐久保护、死亡重生回点。",
|
||||||
"authors": [],
|
"authors": [],
|
||||||
"contact": {},
|
"contact": {},
|
||||||
"license": "All-Rights-Reserved",
|
"license": "All-Rights-Reserved",
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户