diff --git a/README.md b/README.md new file mode 100644 index 0000000..8dc5d1f --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# AutoDig 自动挖矿模组 + +一个基于 Fabric + Baritone 的 Minecraft 客户端自动挖矿模组,内置 Baritone 1.13.1(无需另行安装),支持主世界鱼骨巷道挖矿与下界自动挖矿返程。 + +## 功能特性 + +### 挖矿任务 +- **主世界模式**:在指定 Y 层沿玩家朝向挖鱼骨巷道(主干 + 两侧侧支),自动返回地表;可自定义挖掘层、巷道长度、目标矿物(默认钻石)。 +- **下界模式**:自动进传送门进入下界挖矿(默认远古残骸),挖够数量 / 背包满 / 血量低 / 装备耐久低时自动回主世界;支持无限挖(目标数量 -1)。 + +### 智能辅助(可逐项开关) +- **自动切换工具**:由 Baritone 自动选择最合适的镐/锹等工具。 +- **自动进食**:饥饿度低时自动吃背包食物,跳过金苹果等特殊/珍贵食物。 +- **自动插火把**:隧道黑暗时自动从背包取火把放置,防止刷怪。 +- **自动换备用装备**:护甲/工具耐久过低时自动从背包换上同类备用装备。 +- **提前丢垃圾**:背包接近满(已用 ≥30 格)时就提前丢弃圆石、深板岩等垃圾,为矿物腾出空间,避免被误判"背包已满需返回"。 +- **杀戮光环**:自动攻击附近真正敌对生物,不误伤铁傀儡、僵尸猪灵、末影人等中立生物。 +- **保命优先**:血线过低时暂停挖矿向上撤离,恢复后自动续任务。 +- **装备耐久保护**:护甲/工具耐久低于阈值时暂停或换装,下界模式直接返程。 +- **死亡重生回点**:重生后自动返回死亡地点继续任务。 +- **实时状态 HUD**:挖矿时屏幕左上角实时显示任务、状态、运行时长、血量、饥饿、背包、进度。 + +## 使用说明 + +1. 将 mod jar 放入 `mods` 文件夹(需 Fabric Loader 与 Fabric API)。 +2. 进入游戏后按 **Ctrl + R**(或在按键设置中查看"打开 AutoDig 界面")打开界面。 +3. 选择任务(主世界 / 下界),按需调整设置,点击"开始"。 + +### 命令 +| 命令 | 说明 | +| --- | --- | +| `/autodig gui` | 打开设置界面 | +| `/autodig start overworld` / `start nether` | 直接启动对应任务 | +| `/autodig stop` | 停止当前任务 | +| `/autodig status` | 查看实时状态(任务/状态/时长/死亡/血量/饥饿) | + +## 配置文件 + +配置文件位于 Fabric 配置目录下的 `autodig.json`,也可在游戏内 GUI 修改。 + +## 版本记录 + +- **v2.2.0**:新增实时状态 HUD、自动插火把、`/autodig status` 命令;自动进食跳过特殊食物;背包接近满提前丢垃圾防误判返程;杀戮光环不再误伤中立生物;GUI 补全进食/保命/攻击范围等设置。 +- **v2.1.0**:自动换备用装备 + 背包满自动丢垃圾。 +- **v2.0.x**:GUI 响应式布局、主世界/下界模式分离、装备耐久保护、自定义矿物。 diff --git a/gradle.properties b/gradle.properties index 0f563a9..7b089d6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ yarn_mappings=1.21.4+build.8 loader_version=0.19.3 # Mod Properties -mod_version=2.1.0 +mod_version=2.2.0 maven_group=autodig archives_base_name=autodig diff --git a/src/main/java/autodig/client/AutoDigModClient.java b/src/main/java/autodig/client/AutoDigModClient.java index 63eb3bc..51df446 100644 --- a/src/main/java/autodig/client/AutoDigModClient.java +++ b/src/main/java/autodig/client/AutoDigModClient.java @@ -7,6 +7,7 @@ import autodig.client.config.DigConfig; import autodig.client.config.MineTask; import autodig.client.controller.DigController; import autodig.client.gui.AutoDigGui; +import autodig.client.gui.StatusHud; import com.mojang.brigadier.CommandDispatcher; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.EnvType; @@ -15,6 +16,7 @@ import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallba import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; import net.minecraft.client.MinecraftClient; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; @@ -79,6 +81,8 @@ public final class AutoDigModClient implements ClientModInitializer { }); ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> AutoDigCommands.register(dispatcher)); + // 实时状态 HUD:仅在挖矿任务进行中于左上角渲染 + HudRenderCallback.EVENT.register((context, tickCounter) -> StatusHud.render(context)); } public static AutoDigModClient instance() { diff --git a/src/main/java/autodig/client/command/AutoDigCommands.java b/src/main/java/autodig/client/command/AutoDigCommands.java index da49f18..433318f 100644 --- a/src/main/java/autodig/client/command/AutoDigCommands.java +++ b/src/main/java/autodig/client/command/AutoDigCommands.java @@ -2,17 +2,19 @@ package autodig.client.command; import autodig.client.AutoDigModClient; import autodig.client.config.MineTask; +import autodig.client.controller.DigController; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; /** * AutoDig 客户端命令。 * *

提供 {@code /autodig gui}(打开界面)、{@code /autodig start <任务>} - * (直接启动)、{@code /autodig stop}(停止当前任务)三个快捷入口, - * 方便在未绑定的快捷键场景下使用。

+ * (直接启动)、{@code /autodig stop}(停止当前任务)、{@code /autodig status} + * (查看实时状态)四个快捷入口,方便在未绑定的快捷键场景下使用。

*/ public final class AutoDigCommands { @@ -51,6 +53,33 @@ public final class AutoDigCommands { mod.bridge().cancelAll(); mod.sendToast("AutoDig: 已停止"); return 1; + })) + .then(com.mojang.brigadier.builder.LiteralArgumentBuilder.literal("status") + .executes(ctx -> { + AutoDigModClient mod = AutoDigModClient.instance(); + if (mod == null) { + ctx.getSource().sendFeedback(Text.literal("AutoDig 未初始化")); + return 0; + } + MinecraftClient client = MinecraftClient.getInstance(); + boolean playerOk = client.player != null; + String task = "任务: " + mod.config().task.label(); + String state = "状态: " + mod.controller().statusText(); + String hp = playerOk ? "血量: " + String.format("%.0f%%", + client.player.getHealth() / Math.max(client.player.getMaxHealth(), 1.0) * 100.0) : "血量: --"; + String food = playerOk ? "饥饿: " + client.player.getHungerManager().getFoodLevel() + "/20" : "饥饿: --"; + String time = "时间: " + mod.controller().stats().elapsedString(); + String deaths = "死亡: " + mod.controller().stats().getDeaths(); + ctx.getSource().sendFeedback(Text.literal("§e== AutoDig 状态 ==")); + ctx.getSource().sendFeedback(Text.literal("§7" + task)); + ctx.getSource().sendFeedback(Text.literal("§7" + state)); + ctx.getSource().sendFeedback(Text.literal("§7" + time)); + ctx.getSource().sendFeedback(Text.literal("§7" + deaths)); + if (playerOk) { + ctx.getSource().sendFeedback(Text.literal("§7" + hp)); + ctx.getSource().sendFeedback(Text.literal("§7" + food)); + } + return 1; }))); } diff --git a/src/main/java/autodig/client/config/DigConfig.java b/src/main/java/autodig/client/config/DigConfig.java index 94709b0..123735e 100644 --- a/src/main/java/autodig/client/config/DigConfig.java +++ b/src/main/java/autodig/client/config/DigConfig.java @@ -18,6 +18,8 @@ public class DigConfig { public boolean autoEat = true; /** 饥饿度低于该值时开始进食(20 为满值) */ public int eatHungerThreshold = 8; + /** 自动插火把(隧道黑暗时自动从背包取火把放置,防止刷怪) */ + public boolean autoTorch = true; // ---- 保命/安全 ---- /** 保命优先:生命过低时暂停挖矿并向上撤离 */ @@ -112,6 +114,7 @@ public class DigConfig { c.autoTool = this.autoTool; c.autoEat = this.autoEat; c.eatHungerThreshold = this.eatHungerThreshold; + c.autoTorch = this.autoTorch; c.survivalFirst = this.survivalFirst; c.healthFleeThreshold = this.healthFleeThreshold; c.armorDurabilityProtection = this.armorDurabilityProtection; diff --git a/src/main/java/autodig/client/controller/DigController.java b/src/main/java/autodig/client/controller/DigController.java index bd7f374..f550e05 100644 --- a/src/main/java/autodig/client/controller/DigController.java +++ b/src/main/java/autodig/client/controller/DigController.java @@ -7,9 +7,11 @@ import autodig.client.miner.NetherMiner; import autodig.client.miner.OverworldMiner; import autodig.client.service.AutoEat; import autodig.client.service.AutoGear; +import autodig.client.service.AutoTorch; import autodig.client.service.AutoTrash; import autodig.client.service.DeathRecorder; import autodig.client.service.KillAura; +import autodig.client.service.SessionStats; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; @@ -34,7 +36,9 @@ public final class DigController { private final KillAura killAura = new KillAura(); private final AutoGear autoGear = new AutoGear(); private final AutoTrash autoTrash = new AutoTrash(); + private final AutoTorch autoTorch = new AutoTorch(); private final DeathRecorder deathRecorder = new DeathRecorder(); + private final SessionStats stats = new SessionStats(); private final OverworldMiner overworld = new OverworldMiner(this.bridge); private final NetherMiner nether = new NetherMiner(this.bridge); @@ -56,15 +60,20 @@ public final class DigController { } PlayerEntity player = client.player; - // 常驻能力:杀戮光环、自动进食、背包满丢垃圾、换装冷却 + // 常驻能力:杀戮光环、自动进食、背包满丢垃圾、自动火把、换装冷却 this.killAura.tick(config); this.autoEat.tick(config); this.autoTrash.tick(config); + this.autoTorch.tick(config); this.autoGear.tick(); - // 死亡处理:死亡瞬间暂停任务,重生后按需回到死亡坐标 + // 死亡处理:记录死亡次数,暂停任务,重生后按需回到死亡坐标 + boolean wasDead = player.isDead(); this.deathRecorder.tick(config, this::onRespawn); if (player.isDead()) { + if (!wasDead) { + this.stats.addDeath(); // 刚死亡,计一次 + } this.bridge.cancelAll(); return; } @@ -108,6 +117,7 @@ public final class DigController { if (config.task == MineTask.NONE) { if (this.state != State.IDLE) { this.bridge.cancelAll(); + this.stats.stop(); this.setState(State.IDLE, "任务已停止,进入空闲状态"); } return; @@ -135,10 +145,12 @@ public final class DigController { break; } if (config.task == MineTask.OVERWORLD) { + this.stats.start(); this.overworld.init(config); this.setState(State.OVERWORLD, "开始主世界鱼骨挖矿(Y=" + config.overworldLevel + ",巷道 " + config.tunnelLength + " 格)"); } else if (config.task == MineTask.NETHER) { + this.stats.start(); this.nether.reset(); this.setState(State.NETHER, "开始下界挖矿(目标 " + config.netherTargetCount + " 个)"); } @@ -250,6 +262,7 @@ public final class DigController { private void finish(DigConfig config, String message) { this.bridge.cancelAll(); config.task = MineTask.NONE; + this.stats.stop(); this.setState(State.IDLE, message); } @@ -368,6 +381,26 @@ public final class DigController { return this.bridge; } + /** @return 会话统计(供 HUD/GUI 展示)。 */ + public SessionStats stats() { + return this.stats; + } + + /** @return 主世界鱼骨巷道当前推进到的步数(HUD 进度显示用)。 */ + public int overworldStep() { + return this.overworld.currentStep(); + } + + /** @return 主世界鱼骨巷道总步数(HUD 进度显示用)。 */ + public int overworldTotal() { + return this.overworld.totalSteps(); + } + + /** @return 下界模式本次新挖到的目标矿物数量(HUD 进度显示用)。 */ + public int netherMinedCount() { + return this.nether.getMinedCount(); + } + /** 供 GUI/调试获取当前状态文本(中文描述)。 */ public String statusText() { return switch (this.state) { diff --git a/src/main/java/autodig/client/gui/AutoDigGui.java b/src/main/java/autodig/client/gui/AutoDigGui.java index e8e593f..5699fc3 100644 --- a/src/main/java/autodig/client/gui/AutoDigGui.java +++ b/src/main/java/autodig/client/gui/AutoDigGui.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.BooleanSupplier; import java.util.function.Consumer; +import java.util.function.DoubleSupplier; import java.util.function.IntSupplier; import java.util.function.Supplier; import net.minecraft.client.gui.DrawContext; @@ -114,11 +115,24 @@ public final class AutoDigGui extends Screen { y = this.checkPair(y, "护甲耐久保护", () -> this.config.armorDurabilityProtection, v -> this.config.armorDurabilityProtection = v, "工具耐久保护", () -> this.config.toolDurabilityProtection, v -> this.config.toolDurabilityProtection = v); y = this.checkPair(y, "自动换备用装备", () -> this.config.autoGearSwap, v -> this.config.autoGearSwap = v, - "背包满丢垃圾", () -> this.config.autoTrash, v -> { + "自动插火把", () -> this.config.autoTorch, v -> this.config.autoTorch = v); + y = this.checkPair(y, "背包满丢垃圾", () -> this.config.autoTrash, v -> { this.syncFields(); // 保留输入框中未应用的编辑 this.config.autoTrash = v; this.init(); // 切换后显示/隐藏垃圾设置区 - }); + }, + null, null, null); + y += 10; + + y = this.header(y, "进食/保命/攻击阈值"); + y = this.numberRow(y, "自动进食阈值 (饥饿)", () -> this.config.eatHungerThreshold, + v -> this.config.eatHungerThreshold = v, 1, 20); + y = this.numberRow(y, "保命血量阈值 (%)", () -> this.config.healthFleeThreshold, + v -> this.config.healthFleeThreshold = v, 5, 100); + if (this.config.killAura) { + y = this.doubleRow(y, "杀戮光环范围", () -> this.config.killRange, + v -> this.config.killRange = v, 2.0, 8.0, 0.5); + } y += 10; y = this.header(y, "装备耐久阈值(低于即停,百分比)"); @@ -219,6 +233,23 @@ public final class AutoDigGui extends Screen { return y + 24; } + /** 小数数值行:与 numberRow 相同布局,但支持 0.5 步进的小数值(如攻击范围)。 */ + private int doubleRow(int y, String label, DoubleSupplier getter, Consumer setter, + double min, double max, double step) { + int btn = 20; + this.addScrollable(ButtonWidget.builder(Text.literal("-"), b -> + setter.accept(MathHelper.clamp(getter.getAsDouble() - step, min, max))) + .dimensions(this.panelLeft, 0, btn, btn).build(), this.panelLeft, y); + this.addScrollable(ButtonWidget.builder(Text.literal("+"), b -> + setter.accept(MathHelper.clamp(getter.getAsDouble() + step, min, max))) + .dimensions(this.panelLeft + this.panelW - btn, 0, btn, btn).build(), + this.panelLeft + this.panelW - btn, y); + this.texts.add(new TextEntry(this.panelLeft + btn + 6, y + 6, () -> label, 0xFFFFFF, false, false)); + this.texts.add(new TextEntry(this.panelLeft + this.panelW - btn - 6, y + 6, + () -> String.format("%.1f", getter.getAsDouble()), 0x55FF55, true, false)); + return y + 24; + } + /** 文本输入行:标签 + 输入框(占满面板宽度)。 */ private int fieldRow(int y, String label, String initial, Consumer ref) { this.texts.add(new TextEntry(this.panelLeft, y + 3, () -> label, 0xBBBBBB, false, false)); @@ -369,10 +400,13 @@ public final class AutoDigGui extends Screen { target.survivalFirst = this.config.survivalFirst; target.avoidLava = this.config.avoidLava; target.autoEat = this.config.autoEat; + target.eatHungerThreshold = this.config.eatHungerThreshold; + target.autoTorch = this.config.autoTorch; target.autoTool = this.config.autoTool; target.killAura = this.config.killAura; target.killAuraSafe = this.config.killAuraSafe; target.killAuraInNether = this.config.killAuraInNether; + target.killRange = this.config.killRange; target.respawnReturn = this.config.respawnReturn; target.netherAutoReturn = this.config.netherAutoReturn; target.overworldLevel = this.config.overworldLevel; diff --git a/src/main/java/autodig/client/gui/StatusHud.java b/src/main/java/autodig/client/gui/StatusHud.java new file mode 100644 index 0000000..bbbfbaa --- /dev/null +++ b/src/main/java/autodig/client/gui/StatusHud.java @@ -0,0 +1,109 @@ +package autodig.client.gui; + +import autodig.client.AutoDigModClient; +import autodig.client.config.DigConfig; +import autodig.client.config.MineTask; +import autodig.client.controller.DigController; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.entity.player.PlayerEntity; + +/** + * 游戏内实时状态 HUD(叠加层)。 + * + *

仅在挖矿任务进行中(task != NONE)于屏幕左上角渲染一块半透明面板, + * 实时展示:任务、状态机阶段、运行时长、血量百分比(按阈值变色)、 + * 饥饿值、背包占用、挖掘进度(主世界鱼骨 step/total、下界已挖/目标)。 + * 玩家无需打开 GUI 即可对自动化过程一目了然。

+ * + *

由 {@link net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback} + * 驱动,在 {@link AutoDigModClient#onInitializeClient()} 中注册。

+ */ +public final class StatusHud { + + private static final int LINE_H = 11; + private static final int X = 4; + private static final int Y = 4; + private static final int PANEL_W = 172; + + private StatusHud() { + } + + /** + * 渲染挖矿状态 HUD(仅在任务进行中显示)。 + * + * @param context 绘制上下文(由 HudRenderCallback 提供) + */ + public static void render(DrawContext context) { + AutoDigModClient mod = AutoDigModClient.instance(); + if (mod == null) { + return; + } + DigConfig config = mod.config(); + if (config.task == MineTask.NONE) { + return; + } + DigController controller = mod.controller(); + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player == null || client.textRenderer == null) { + return; + } + PlayerEntity player = client.player; + + int y = Y; + // 半透明背景面板 + context.fill(X - 3, Y - 3, X + PANEL_W, Y + LINE_H * 8 + 2, 0x80000000); + + // 标题 + context.drawTextWithShadow(client.textRenderer, "\u00A7e== AutoDig ==", X, y, 0xFFFFFF); + y += LINE_H; + + // 任务 + context.drawTextWithShadow(client.textRenderer, "\u00A77\u4efb\u52a1: \u00A7f" + config.task.label(), X, y, 0xFFFFFF); + y += LINE_H; + + // 状态 + context.drawTextWithShadow(client.textRenderer, "\u00A77\u72b6\u6001: \u00A7f" + controller.statusText(), X, y, 0xFFFFFF); + y += LINE_H; + + // 运行时长 + context.drawTextWithShadow(client.textRenderer, "\u00A77\u65f6\u95f4: \u00A7f" + controller.stats().elapsedString(), X, y, 0xFFFFFF); + y += LINE_H; + + // 血量(按阈值变色) + int hp = (int) Math.round(player.getHealth() / Math.max(player.getMaxHealth(), 1.0) * 100); + String hpCode = hp < 30 ? "\u00A7c" : hp < 60 ? "\u00A7e" : "\u00A7a"; + context.drawTextWithShadow(client.textRenderer, "\u00A77\u8840\u91cf: " + hpCode + hp + "%", X, y, 0xFFFFFF); + y += LINE_H; + + // 饥饿 + int food = player.getHungerManager().getFoodLevel(); + String foodCode = food <= config.eatHungerThreshold ? "\u00A7c" : "\u00A7a"; + context.drawTextWithShadow(client.textRenderer, "\u00A77\u9965\u997f: " + foodCode + food + "/20", X, y, 0xFFFFFF); + y += LINE_H; + + // 背包占用 + int used = 0; + for (int i = 0; i < 36; i++) { + if (!player.getInventory().main.get(i).isEmpty()) { + used++; + } + } + String invCode = used >= 36 ? "\u00A7c" : used >= 30 ? "\u00A7e" : "\u00A7f"; + context.drawTextWithShadow(client.textRenderer, "\u00A77\u80cc\u5305: " + invCode + used + "/36", X, y, 0xFFFFFF); + y += LINE_H; + + // 挖掘进度 + String progress; + if (config.task == MineTask.OVERWORLD) { + progress = controller.overworldStep() + "/" + controller.overworldTotal(); + } else if (config.task == MineTask.NETHER) { + int target = config.netherTargetCount; + int mined = controller.netherMinedCount(); + progress = mined + (target > 0 ? "/" + target : ""); + } else { + progress = "--"; + } + context.drawTextWithShadow(client.textRenderer, "\u00A77\u8fdb\u5ea6: \u00A7f" + progress, X, y, 0xFFFFFF); + } +} diff --git a/src/main/java/autodig/client/miner/OverworldMiner.java b/src/main/java/autodig/client/miner/OverworldMiner.java index 27f50b9..a15ec09 100644 --- a/src/main/java/autodig/client/miner/OverworldMiner.java +++ b/src/main/java/autodig/client/miner/OverworldMiner.java @@ -174,6 +174,16 @@ public final class OverworldMiner { return this.done; } + /** @return 当前推进到的计划步数(从 0 开始) */ + public int currentStep() { + return this.step; + } + + /** @return 鱼骨计划总步数 */ + public int totalSteps() { + return this.plan.size(); + } + /** @return 完成原因是否为「背包已满」 */ public boolean isFull() { return this.full; diff --git a/src/main/java/autodig/client/service/AutoEat.java b/src/main/java/autodig/client/service/AutoEat.java index 1a7011c..1d4836b 100644 --- a/src/main/java/autodig/client/service/AutoEat.java +++ b/src/main/java/autodig/client/service/AutoEat.java @@ -2,8 +2,11 @@ package autodig.client.service; import autodig.client.config.DigConfig; import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.item.consume.UseAction; import net.minecraft.util.Hand; @@ -13,11 +16,14 @@ import net.minecraft.util.Hand; *

饥饿度低于阈值时,自动从快捷栏/主背包挑选一件食物并持续"使用手指"进食。 * 食物在主背包时,先将其与当前快捷栏槽位交换,下一帧再吃。 * 已在使用物品(正在咀嚼)时不会重复触发,避免打断进食节奏。

+ * + *

智能选择:跳过金苹果、附魔金苹果、紫颂果等有特殊效果/珍贵的食物, + * 优先吃普通口粮,避免把保命物品当普通食物浪费。

*/ public final class AutoEat { /** - * 每帧调用。饥饿不够时在快捷栏/主背包里找食物并吃掉。 + * 每帧调用。饥饿不够时在快捷栏/主背包里找普通食物并吃掉。 * * @param config 挖矿配置 */ @@ -26,7 +32,7 @@ public final class AutoEat { if (client.player == null) { return; } - net.minecraft.entity.player.PlayerEntity player = client.player; + PlayerEntity player = client.player; // 已在进食(用于吞咽)时等待,避免反复打断 if (player.isUsingItem()) { return; @@ -36,10 +42,10 @@ public final class AutoEat { return; } PlayerInventory inv = player.getInventory(); - // 快捷栏 + 主背包共 36 格找食物 + // 快捷栏 + 主背包共 36 格找普通食物 for (int slot = 0; slot < 36; slot++) { ItemStack stack = inv.getStack(slot); - if (stack.isEmpty() || stack.getUseAction() != UseAction.EAT) { + if (stack.isEmpty() || !isSafeFood(stack)) { continue; } if (slot >= 9) { @@ -56,4 +62,18 @@ public final class AutoEat { return; } } + + /** + * 是否为可安全自动进食的普通食物。 + * 排除金苹果/附魔金苹果(有再生等特殊效果,价值高)、紫颂果(传送副作用) + * 等特殊物品,避免自动进食把保命物资白白吃掉。 + */ + private static boolean isSafeFood(ItemStack stack) { + Item item = stack.getItem(); + if (item == Items.GOLDEN_APPLE || item == Items.ENCHANTED_GOLDEN_APPLE + || item == Items.CHORUS_FRUIT) { + return false; + } + return stack.getUseAction() == UseAction.EAT; + } } \ No newline at end of file diff --git a/src/main/java/autodig/client/service/AutoTorch.java b/src/main/java/autodig/client/service/AutoTorch.java new file mode 100644 index 0000000..56124ed --- /dev/null +++ b/src/main/java/autodig/client/service/AutoTorch.java @@ -0,0 +1,116 @@ +package autodig.client.service; + +import autodig.client.config.DigConfig; +import autodig.client.config.MineTask; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; + +/** + * 自动火把服务。 + * + *

在挖矿任务进行中,每 3 秒检测一次玩家所在位置的亮度;若光照低于阈值(8), + * 自动从背包中取火把并放置在脚下或相邻方块的顶部,防止隧道刷怪。 + * 光照充足时自动跳过,已放火把的区域光照提升后不会再重复放置。

+ * + *

放置通过 {@code interactBlock} 走正规网络包,单人/多人均可用。 + * 火把放置的可行性由 {@link BlockState#canPlaceAt} 委托原版逻辑判定, + * 无需自行处理"方块顶部是否可承托"等复杂条件。

+ */ +public final class AutoTorch { + + /** 检查间隔(tick),约 3 秒 */ + private static final int CHECK_INTERVAL = 60; + /** 低于此光照值视为需要插火把(原版刷怪上限是 0,取 8 留余量) */ + private static final int DARK_THRESHOLD = 8; + + private int tickCounter; + + /** 每帧调用。黑暗时自动放置火把。 */ + public void tick(DigConfig config) { + if (!config.autoTorch) { + return; + } + this.tickCounter++; + if (this.tickCounter < CHECK_INTERVAL) { + return; + } + this.tickCounter = 0; + + MinecraftClient client = MinecraftClient.getInstance(); + if (client.player == null || client.world == null || client.interactionManager == null) { + return; + } + // 仅在挖矿任务进行中生效 + if (config.task == MineTask.NONE) { + return; + } + if (client.currentScreen != null) { + return; + } + PlayerEntity player = client.player; + BlockPos feet = player.getBlockPos(); + + // 光照充足则跳过(火把光 14,6 格内光照 >= 8) + if (client.world.getLightLevel(feet) >= DARK_THRESHOLD) { + return; + } + + // 在背包中找火把 + int torchSlot = -1; + for (int i = 0; i < 36; i++) { + ItemStack s = player.getInventory().main.get(i); + if (!s.isEmpty() && s.getItem() == Items.TORCH) { + torchSlot = i; + break; + } + } + if (torchSlot < 0) { + return; + } + + // 找一个可放置火把的位置(先试脚下,再试四相邻) + BlockState torchState = Blocks.TORCH.getDefaultState(); + BlockPos clickPos = null; + BlockPos feetDown = feet.down(); + if (torchState.canPlaceAt(client.world, feet)) { + clickPos = feetDown; + } + if (clickPos == null) { + for (Direction d : new Direction[]{Direction.NORTH, Direction.SOUTH, + Direction.EAST, Direction.WEST}) { + BlockPos place = feet.offset(d); + if (torchState.canPlaceAt(client.world, place)) { + clickPos = place.down(); + break; + } + } + } + if (clickPos == null) { + return; + } + + // 切到火把所在槽位(主背包的先换到快捷栏,下一帧再放) + if (torchSlot >= 9) { + player.getInventory().swapSlotWithHotbar(torchSlot); + return; + } + if (player.getInventory().selectedSlot != torchSlot) { + player.getInventory().selectedSlot = torchSlot; + return; + } + + // 在支撑方块顶面放置火把 + Vec3d hitVec = Vec3d.ofCenter(clickPos).add(0, 0.5, 0); + BlockHitResult hit = new BlockHitResult(hitVec, Direction.UP, clickPos, false); + client.interactionManager.interactBlock(client.player, Hand.MAIN_HAND, hit); + } +} diff --git a/src/main/java/autodig/client/service/AutoTrash.java b/src/main/java/autodig/client/service/AutoTrash.java index 14147a9..b27fb78 100644 --- a/src/main/java/autodig/client/service/AutoTrash.java +++ b/src/main/java/autodig/client/service/AutoTrash.java @@ -19,18 +19,20 @@ import net.minecraft.screen.slot.SlotActionType; /** * 背包垃圾自动清理服务。 * - *

背包满时,把 {@code trashItems} 清单里的垃圾物品丢出背包(圆石、凝灰岩、 - * 深板岩等挖掘副产物)。方块类垃圾保留 {@code trashReserve} 个供 Baritone - * 搭路/垫脚使用;若满背时额度之外没有别的垃圾可丢,则只丢一组腾出空间, - * 尽量保住搭路方块。丢弃通过 {@code clickSlot(THROW)} 走正规网络包, - * 单人/多人均可用。

+ *

背包接近满(已用格数达到阈值)时就主动把 {@code trashItems} 清单里的 + * 垃圾物品丢出背包(圆石、凝灰岩、深板岩等挖掘副产物),为挖到的矿物提前 + * 腾出空间,避免背包彻底满后触发"背包已满需返回"而中断挖矿。方块类垃圾 + * 保留 {@code trashReserve} 个供 Baritone 搭路/垫脚使用。丢弃通过 + * {@code clickSlot(THROW)} 走正规网络包,单人/多人均可用。

*/ public final class AutoTrash { /** 每 tick 最多丢弃的物品组数,避免一次产生过多数据包 */ private static final int MAX_DROPS_PER_TICK = 4; + /** 背包已用格数达到该值(共 36 格)即开始丢垃圾,留出余量防误判满包 */ + private static final int NEARLY_FULL_SLOTS = 30; - /** 每帧调用:背包满时自动丢弃垃圾物品。 */ + /** 每帧调用:背包接近满时提前丢弃垃圾物品。 */ public void tick(DigConfig config) { MinecraftClient client = MinecraftClient.getInstance(); if (client.player == null || client.world == null || client.interactionManager == null) { @@ -50,7 +52,8 @@ public final class AutoTrash { if (!handler.getCursorStack().isEmpty()) { return; } - if (!this.inventoryFull(player)) { + // 背包接近满才开始丢,避免背包还没满就误丢 + if (this.usedSlots(player) < NEARLY_FULL_SLOTS) { return; } Set trash = new HashSet<>(config.getTrashItemList()); @@ -115,13 +118,15 @@ public final class AutoTrash { } } - private boolean inventoryFull(PlayerEntity player) { + /** 统计背包已用格数(快捷栏 + 主背包 0-35,共 36 格)。 */ + private int usedSlots(PlayerEntity player) { + int used = 0; for (int i = 0; i < 36; i++) { - if (player.getInventory().main.get(i).isEmpty()) { - return false; + if (!player.getInventory().main.get(i).isEmpty()) { + used++; } } - return true; + return used; } /** 按对象引用在 ScreenHandler 里找槽位下标。 */ diff --git a/src/main/java/autodig/client/service/KillAura.java b/src/main/java/autodig/client/service/KillAura.java index 2adb73f..3afc009 100644 --- a/src/main/java/autodig/client/service/KillAura.java +++ b/src/main/java/autodig/client/service/KillAura.java @@ -4,6 +4,7 @@ import autodig.client.config.DigConfig; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.mob.Angerable; import net.minecraft.entity.mob.Monster; import net.minecraft.util.Hand; import net.minecraft.util.math.Box; @@ -14,9 +15,12 @@ import java.util.concurrent.ThreadLocalRandom; /** * 杀戮光环服务。 * - *

在开关开启时,自动攻击以玩家为中心、指定半径内最近的敌对生物 - * ({@link Monster})。攻击前会转向目标方向;"安全模式"开启时, - * 玩家生命过低则停止攻击、保命优先。

+ *

在开关开启时,自动攻击以玩家为中心、指定半径内最近的敌对生物。 + * 只攻击真正的敌对生物({@link Monster}),并排除中立生物 + * ({@link Angerable},如僵尸猪灵、末影人、狼等),避免误伤本不会 + * 主动攻击玩家的中立单位;铁傀儡等非 Monster 类天然不在攻击范围。 + * 攻击前会转向目标方向;"安全模式"开启时,玩家生命过低则停止攻击、 + * 保命优先。

*/ public final class KillAura { @@ -72,13 +76,14 @@ public final class KillAura { this.attackCooldownTicks = 3 + ThreadLocalRandom.current().nextInt(4); } - /** 在玩家周围范围内找最近的敌对生物。 */ + /** 在玩家周围范围内找最近的、真正敌对(非中立)的生物。 */ private Entity findNearestHostile(MinecraftClient client, LivingEntity player, double range) { range = Math.max(2.0, range); Box box = player.getBoundingBox().expand(range); Entity best = null; double bestDist = Double.MAX_VALUE; - for (Entity e : client.world.getEntitiesByClass(Entity.class, box, e -> e instanceof Monster && e.isAlive())) { + for (Entity e : client.world.getEntitiesByClass(Entity.class, box, + e -> e instanceof Monster && !(e instanceof Angerable) && e.isAlive())) { double d = e.squaredDistanceTo(player); if (d < bestDist) { bestDist = d; diff --git a/src/main/java/autodig/client/service/SessionStats.java b/src/main/java/autodig/client/service/SessionStats.java new file mode 100644 index 0000000..a3038f0 --- /dev/null +++ b/src/main/java/autodig/client/service/SessionStats.java @@ -0,0 +1,51 @@ +package autodig.client.service; + +/** + * 会话统计服务。 + * + *

追踪本次挖矿任务的关键运行指标:开始时间、死亡次数。 + * 在 HUD 与 GUI 中展示,让玩家对自动化过程有直观感知。 + * 任务开始时 {@link #start()},任务结束或停止时 {@link #stop()}。

+ */ +public final class SessionStats { + + private long startTime; + private int deaths; + private boolean active; + + /** 开始一次新的统计会话(清零所有计数器)。 */ + public void start() { + this.startTime = System.currentTimeMillis(); + this.deaths = 0; + this.active = true; + } + + /** 结束当前会话。 */ + public void stop() { + this.active = false; + } + + /** 记录一次死亡。 */ + public void addDeath() { + this.deaths++; + } + + public boolean isActive() { + return this.active; + } + + public int getDeaths() { + return this.deaths; + } + + /** @return 已运行时长字符串(如 "12:34"),未开始时返回 "--" */ + public String elapsedString() { + if (!this.active || this.startTime == 0) { + return "--"; + } + long elapsed = (System.currentTimeMillis() - this.startTime) / 1000; + long m = elapsed / 60; + long s = elapsed % 60; + return m + ":" + (s < 10 ? "0" + s : s); + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 07edc65..49df15e 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -3,7 +3,7 @@ "id": "autodig", "version": "${version}", "name": "AutoDig", - "description": "自动挖矿模组(内置 Baritone 1.13.1,无需另行安装):主世界模式与下界模式,均可自定义目标矿物。主世界鱼骨巷道挖矿,下界自动进出传送门挖矿并支持返程。支持自动切换工具、自动进食、杀戮光环、保命优先、优先绕开岩浆、装备耐久保护(自动换备用装备)、背包满自动丢垃圾、死亡重生回点。", + "description": "自动挖矿模组(内置 Baritone 1.13.1,无需另行安装):主世界模式与下界模式,均可自定义目标矿物。主世界鱼骨巷道挖矿,下界自动进出传送门挖矿并支持返程。支持自动切换工具、自动进食(避开特殊食物)、自动插火把防刷怪、杀戮光环(不误伤中立生物)、保命优先、优先绕开岩浆、装备耐久保护(自动换备用装备)、背包近满提前丢垃圾防误判返程、死亡重生回点、实时状态 HUD。", "authors": [], "contact": {}, "license": "All-Rights-Reserved",