feat: 智能升级 v2.2.0 - HUD/AutoTorch/AutoEat 智能食物/提前丢垃圾/剔除中立生物

核心改进:
- 实时状态 HUD(左上角面板:任务/状态/时长/血量/饥饿/背包/进度)
- 自动插火把(黑暗隧道自动防灾,可开关)
- 自动进食跳过金苹果/附魔金苹果/紫颂果等特殊食物
- 背包接近满(≥30格)即提前丢垃圾,防误判满包返回
- 杀戮光环排除 Angerable 中立生物(僵尸猪灵/末影人/狼等)
- 新增 /autodig status 命令
- GUI 补全设置:自动插火把、进食阈值、保命血量、攻击范围
- 会话统计(时长/死亡次数)联动 HUD 与命令
- 补全 README.md 文档
- 版本升至 2.2.0
这个提交包含在:
lyizai
2026-08-30 22:36:35 +08:00
父节点 a297d2714d
当前提交 4135cdd117
共修改 15 个文件,包含 492 行新增和 28 行删除
@@ -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);
}
}
+1 -1
查看文件
@@ -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",