feat: 智能升级 v2.2.0 - HUD/AutoTorch/AutoEat 智能食物/提前丢垃圾/剔除中立生物
核心改进: - 实时状态 HUD(左上角面板:任务/状态/时长/血量/饥饿/背包/进度) - 自动插火把(黑暗隧道自动防灾,可开关) - 自动进食跳过金苹果/附魔金苹果/紫颂果等特殊食物 - 背包接近满(≥30格)即提前丢垃圾,防误判满包返回 - 杀戮光环排除 Angerable 中立生物(僵尸猪灵/末影人/狼等) - 新增 /autodig status 命令 - GUI 补全设置:自动插火把、进食阈值、保命血量、攻击范围 - 会话统计(时长/死亡次数)联动 HUD 与命令 - 补全 README.md 文档 - 版本升至 2.2.0
这个提交包含在:
@@ -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 响应式布局、主世界/下界模式分离、装备耐久保护、自定义矿物。
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 客户端命令。
|
||||
*
|
||||
* <p>提供 {@code /autodig gui}(打开界面)、{@code /autodig start <任务>}
|
||||
* (直接启动)、{@code /autodig stop}(停止当前任务)三个快捷入口,
|
||||
* 方便在未绑定的快捷键场景下使用。</p>
|
||||
* (直接启动)、{@code /autodig stop}(停止当前任务)、{@code /autodig status}
|
||||
* (查看实时状态)四个快捷入口,方便在未绑定的快捷键场景下使用。</p>
|
||||
*/
|
||||
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.<FabricClientCommandSource>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;
|
||||
})));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Double> 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<TextFieldWidget> 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;
|
||||
|
||||
@@ -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(叠加层)。
|
||||
*
|
||||
* <p>仅在挖矿任务进行中(task != NONE)于屏幕左上角渲染一块半透明面板,
|
||||
* 实时展示:任务、状态机阶段、运行时长、血量百分比(按阈值变色)、
|
||||
* 饥饿值、背包占用、挖掘进度(主世界鱼骨 step/total、下界已挖/目标)。
|
||||
* 玩家无需打开 GUI 即可对自动化过程一目了然。</p>
|
||||
*
|
||||
* <p>由 {@link net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback}
|
||||
* 驱动,在 {@link AutoDigModClient#onInitializeClient()} 中注册。</p>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
* <p>饥饿度低于阈值时,自动从快捷栏/主背包挑选一件食物并持续"使用手指"进食。
|
||||
* 食物在主背包时,先将其与当前快捷栏槽位交换,下一帧再吃。
|
||||
* 已在使用物品(正在咀嚼)时不会重复触发,避免打断进食节奏。</p>
|
||||
*
|
||||
* <p>智能选择:跳过金苹果、附魔金苹果、紫颂果等有特殊效果/珍贵的食物,
|
||||
* 优先吃普通口粮,避免把保命物品当普通食物浪费。</p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 自动火把服务。
|
||||
*
|
||||
* <p>在挖矿任务进行中,每 3 秒检测一次玩家所在位置的亮度;若光照低于阈值(8),
|
||||
* 自动从背包中取火把并放置在脚下或相邻方块的顶部,防止隧道刷怪。
|
||||
* 光照充足时自动跳过,已放火把的区域光照提升后不会再重复放置。</p>
|
||||
*
|
||||
* <p>放置通过 {@code interactBlock} 走正规网络包,单人/多人均可用。
|
||||
* 火把放置的可行性由 {@link BlockState#canPlaceAt} 委托原版逻辑判定,
|
||||
* 无需自行处理"方块顶部是否可承托"等复杂条件。</p>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -19,18 +19,20 @@ import net.minecraft.screen.slot.SlotActionType;
|
||||
/**
|
||||
* 背包垃圾自动清理服务。
|
||||
*
|
||||
* <p>背包满时,把 {@code trashItems} 清单里的垃圾物品丢出背包(圆石、凝灰岩、
|
||||
* 深板岩等挖掘副产物)。方块类垃圾保留 {@code trashReserve} 个供 Baritone
|
||||
* 搭路/垫脚使用;若满背时额度之外没有别的垃圾可丢,则只丢一组腾出空间,
|
||||
* 尽量保住搭路方块。丢弃通过 {@code clickSlot(THROW)} 走正规网络包,
|
||||
* 单人/多人均可用。</p>
|
||||
* <p>背包接近满(已用格数达到阈值)时就主动把 {@code trashItems} 清单里的
|
||||
* 垃圾物品丢出背包(圆石、凝灰岩、深板岩等挖掘副产物),为挖到的矿物提前
|
||||
* 腾出空间,避免背包彻底满后触发"背包已满需返回"而中断挖矿。方块类垃圾
|
||||
* 保留 {@code trashReserve} 个供 Baritone 搭路/垫脚使用。丢弃通过
|
||||
* {@code clickSlot(THROW)} 走正规网络包,单人/多人均可用。</p>
|
||||
*/
|
||||
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<String> 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 里找槽位下标。 */
|
||||
|
||||
@@ -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;
|
||||
/**
|
||||
* 杀戮光环服务。
|
||||
*
|
||||
* <p>在开关开启时,自动攻击以玩家为中心、指定半径内最近的敌对生物
|
||||
* ({@link Monster})。攻击前会转向目标方向;"安全模式"开启时,
|
||||
* 玩家生命过低则停止攻击、保命优先。</p>
|
||||
* <p>在开关开启时,自动攻击以玩家为中心、指定半径内最近的敌对生物。
|
||||
* 只攻击真正的敌对生物({@link Monster}),并排除中立生物
|
||||
* ({@link Angerable},如僵尸猪灵、末影人、狼等),避免误伤本不会
|
||||
* 主动攻击玩家的中立单位;铁傀儡等非 Monster 类天然不在攻击范围。
|
||||
* 攻击前会转向目标方向;"安全模式"开启时,玩家生命过低则停止攻击、
|
||||
* 保命优先。</p>
|
||||
*/
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package autodig.client.service;
|
||||
|
||||
/**
|
||||
* 会话统计服务。
|
||||
*
|
||||
* <p>追踪本次挖矿任务的关键运行指标:开始时间、死亡次数。
|
||||
* 在 HUD 与 GUI 中展示,让玩家对自动化过程有直观感知。
|
||||
* 任务开始时 {@link #start()},任务结束或停止时 {@link #stop()}。</p>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
在新工单中引用
屏蔽一个用户