feat: v2.2.1 主世界找矿绕道 + 工具适用性检查;移除自动火把

- 移除自动火把功能(按反馈不需要)
- 主世界挖矿新增矿物绕道:每秒扫描周围10格目标矿物并自动绕道挖掉,
  解决巷道壁旁钻石不挖的问题(此前只走巷道不主动找矿)
- 新增工具适用性检查:用 isSuitableFor 校验手持工具能否采掘目标矿物,
  远古残骸等自动换钻石镐;无合适工具时提示并暂停矿物挖掘
- 鱼骨支巷长度改为可配置 branchLength(默认10,原固定6)
- 目标矿物 id 兼容 minecraft: 前缀(带不带均可识别)
这个提交包含在:
lyizai
2026-08-31 12:47:26 +08:00
父节点 4135cdd117
当前提交 61c9384881
共修改 10 个文件,包含 294 行新增和 156 行删除
+6 -4
查看文件
@@ -5,13 +5,14 @@
## 功能特性 ## 功能特性
### 挖矿任务 ### 挖矿任务
- **主世界模式**:在指定 Y 层沿玩家朝向挖鱼骨巷道(主干 + 两侧侧支),自动返回地表;可自定义挖掘层、巷道长度、目标矿物(默认钻石)。 - **主世界模式**:在指定 Y 层沿玩家朝向挖鱼骨巷道(主干 + 两侧侧支),自动返回地表;可自定义挖掘层、主干长度、支巷长度、目标矿物(默认钻石)。巷道推进同时会**扫描周围 10 格的目标矿物并自动绕道挖掉**,巷道壁旁的钻石不漏挖。
- **下界模式**:自动进传送门进入下界挖矿(默认远古残骸),挖够数量 / 背包满 / 血量低 / 装备耐久低时自动回主世界;支持无限挖(目标数量 -1)。 - **下界模式**:自动进传送门进入下界挖矿(默认远古残骸),挖够数量 / 背包满 / 血量低 / 装备耐久低时自动回主世界;支持无限挖(目标数量 -1)。
- **目标矿物 id**:带不带 `minecraft:` 前缀均可识别(如 `diamond_ore` 与 `minecraft:diamond_ore` 等价)。
### 智能辅助(可逐项开关) ### 智能辅助(可逐项开关)
- **自动切换工具**:由 Baritone 自动选择最合适的镐/锹等工具。 - **工具自动选择**:挖掘前校验手持工具能否采掘目标矿物(如远古残骸必须钻石镐以上,铁镐只会白挖不掉落),不适用时自动从背包换上能采掘且耐久最高的工具;没有合适工具时提示并暂停矿物挖掘,放入工具后自动恢复。
- **自动切换工具**:由 Baritone 在挖掘途中自动选择最合适的工具。
- **自动进食**:饥饿度低时自动吃背包食物,跳过金苹果等特殊/珍贵食物。 - **自动进食**:饥饿度低时自动吃背包食物,跳过金苹果等特殊/珍贵食物。
- **自动插火把**:隧道黑暗时自动从背包取火把放置,防止刷怪。
- **自动换备用装备**:护甲/工具耐久过低时自动从背包换上同类备用装备。 - **自动换备用装备**:护甲/工具耐久过低时自动从背包换上同类备用装备。
- **提前丢垃圾**:背包接近满(已用 ≥30 格)时就提前丢弃圆石、深板岩等垃圾,为矿物腾出空间,避免被误判"背包已满需返回"。 - **提前丢垃圾**:背包接近满(已用 ≥30 格)时就提前丢弃圆石、深板岩等垃圾,为矿物腾出空间,避免被误判"背包已满需返回"。
- **杀戮光环**:自动攻击附近真正敌对生物,不误伤铁傀儡、僵尸猪灵、末影人等中立生物。 - **杀戮光环**:自动攻击附近真正敌对生物,不误伤铁傀儡、僵尸猪灵、末影人等中立生物。
@@ -40,6 +41,7 @@
## 版本记录 ## 版本记录
- **v2.2.0**:新增实时状态 HUD、自动插火把、`/autodig status` 命令;自动进食跳过特殊食物;背包接近满提前丢垃圾防误判返程;杀戮光环不再误伤中立生物;GUI 补全进食/保命/攻击范围等设置。 - **v2.2.1**:移除自动火把;新增主世界矿物绕道挖掘(附近 10 格目标矿物自动挖掉,不漏矿);新增工具适用性检查(远古残骸等自动换钻石镐);支巷长度可配置(默认 10);矿物 id 兼容 `minecraft:` 前缀。
- **v2.2.0**:新增实时状态 HUD、`/autodig status` 命令;自动进食跳过特殊食物;背包接近满提前丢垃圾防误判返程;杀戮光环不再误伤中立生物;GUI 补全进食/保命/攻击范围等设置。
- **v2.1.0**:自动换备用装备 + 背包满自动丢垃圾。 - **v2.1.0**:自动换备用装备 + 背包满自动丢垃圾。
- **v2.0.x**:GUI 响应式布局、主世界/下界模式分离、装备耐久保护、自定义矿物。 - **v2.0.x**:GUI 响应式布局、主世界/下界模式分离、装备耐久保护、自定义矿物。
+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=2.2.0 mod_version=2.2.1
maven_group=autodig maven_group=autodig
archives_base_name=autodig archives_base_name=autodig
@@ -77,6 +77,7 @@ public final class ConfigStore {
} }
c.overworldLevel = clamp(c.overworldLevel, -64, 320); c.overworldLevel = clamp(c.overworldLevel, -64, 320);
c.tunnelLength = clamp(c.tunnelLength, 4, 512); c.tunnelLength = clamp(c.tunnelLength, 4, 512);
c.branchLength = clamp(c.branchLength, 1, 64);
c.netherTargetCount = clamp(c.netherTargetCount, -1, 512); c.netherTargetCount = clamp(c.netherTargetCount, -1, 512);
if (c.overworldOres == null || c.overworldOres.isBlank()) { if (c.overworldOres == null || c.overworldOres.isBlank()) {
c.overworldOres = "diamond_ore,deepslate_diamond_ore"; c.overworldOres = "diamond_ore,deepslate_diamond_ore";
@@ -18,8 +18,6 @@ public class DigConfig {
public boolean autoEat = true; public boolean autoEat = true;
/** 饥饿度低于该值时开始进食(20 为满值) */ /** 饥饿度低于该值时开始进食(20 为满值) */
public int eatHungerThreshold = 8; public int eatHungerThreshold = 8;
/** 自动插火把(隧道黑暗时自动从背包取火把放置,防止刷怪) */
public boolean autoTorch = true;
// ---- 保命/安全 ---- // ---- 保命/安全 ----
/** 保命优先:生命过低时暂停挖矿并向上撤离 */ /** 保命优先:生命过低时暂停挖矿并向上撤离 */
@@ -66,7 +64,9 @@ public class DigConfig {
public int overworldLevel = -58; public int overworldLevel = -58;
/** 鱼骨主干巷道长度(方块) */ /** 鱼骨主干巷道长度(方块) */
public int tunnelLength = 60; public int tunnelLength = 60;
/** 主世界要挖的矿物方块 ID 列表(逗号分隔,如 "diamond_ore,deepslate_diamond_ore") */ /** 鱼骨侧支巷道长度(方块);与主干间距 8 相配时 8 为全覆盖、10+ 有少量重叠覆盖 */
public int branchLength = 10;
/** 主世界要挖的矿物方块 ID 列表(逗号分隔,带不带 "minecraft:" 前缀均可) */
public String overworldOres = "diamond_ore,deepslate_diamond_ore"; public String overworldOres = "diamond_ore,deepslate_diamond_ore";
// ---- 下界模式参数 ---- // ---- 下界模式参数 ----
@@ -114,7 +114,6 @@ public class DigConfig {
c.autoTool = this.autoTool; c.autoTool = this.autoTool;
c.autoEat = this.autoEat; c.autoEat = this.autoEat;
c.eatHungerThreshold = this.eatHungerThreshold; c.eatHungerThreshold = this.eatHungerThreshold;
c.autoTorch = this.autoTorch;
c.survivalFirst = this.survivalFirst; c.survivalFirst = this.survivalFirst;
c.healthFleeThreshold = this.healthFleeThreshold; c.healthFleeThreshold = this.healthFleeThreshold;
c.armorDurabilityProtection = this.armorDurabilityProtection; c.armorDurabilityProtection = this.armorDurabilityProtection;
@@ -7,7 +7,6 @@ import autodig.client.miner.NetherMiner;
import autodig.client.miner.OverworldMiner; import autodig.client.miner.OverworldMiner;
import autodig.client.service.AutoEat; import autodig.client.service.AutoEat;
import autodig.client.service.AutoGear; import autodig.client.service.AutoGear;
import autodig.client.service.AutoTorch;
import autodig.client.service.AutoTrash; import autodig.client.service.AutoTrash;
import autodig.client.service.DeathRecorder; import autodig.client.service.DeathRecorder;
import autodig.client.service.KillAura; import autodig.client.service.KillAura;
@@ -36,7 +35,6 @@ public final class DigController {
private final KillAura killAura = new KillAura(); private final KillAura killAura = new KillAura();
private final AutoGear autoGear = new AutoGear(); private final AutoGear autoGear = new AutoGear();
private final AutoTrash autoTrash = new AutoTrash(); private final AutoTrash autoTrash = new AutoTrash();
private final AutoTorch autoTorch = new AutoTorch();
private final DeathRecorder deathRecorder = new DeathRecorder(); private final DeathRecorder deathRecorder = new DeathRecorder();
private final SessionStats stats = new SessionStats(); private final SessionStats stats = new SessionStats();
private final OverworldMiner overworld = new OverworldMiner(this.bridge); private final OverworldMiner overworld = new OverworldMiner(this.bridge);
@@ -46,6 +44,8 @@ public final class DigController {
private BlockPos resumeTarget; private BlockPos resumeTarget;
private long lastIssue; private long lastIssue;
private long lastWarn; private long lastWarn;
/** 上一次"没有合适工具"提示的时间戳(节流) */
private long lastToolWarn;
/** 返回死亡点的截止时间(超时后直接续任务,避免不可达时死循环) */ /** 返回死亡点的截止时间(超时后直接续任务,避免不可达时死循环) */
private long returnDeadline; private long returnDeadline;
/** 装备耐久保护触发前所处的状态,用于恢复后回到该状态 */ /** 装备耐久保护触发前所处的状态,用于恢复后回到该状态 */
@@ -60,11 +60,10 @@ public final class DigController {
} }
PlayerEntity player = client.player; PlayerEntity player = client.player;
// 常驻能力:杀戮光环、自动进食、背包满丢垃圾、自动火把、换装冷却 // 常驻能力:杀戮光环、自动进食、背包满丢垃圾、换装冷却
this.killAura.tick(config); this.killAura.tick(config);
this.autoEat.tick(config); this.autoEat.tick(config);
this.autoTrash.tick(config); this.autoTrash.tick(config);
this.autoTorch.tick(config);
this.autoGear.tick(); this.autoGear.tick();
// 死亡处理:记录死亡次数,暂停任务,重生后按需回到死亡坐标 // 死亡处理:记录死亡次数,暂停任务,重生后按需回到死亡坐标
@@ -133,10 +132,29 @@ public final class DigController {
return; return;
} }
this.drive(config, player); // 工具适用性保障:确保手持工具能采掘目标矿物(如远古残骸必须钻石镐)
boolean canMineOres = true;
if (config.autoTool) {
canMineOres = this.autoGear.ensureToolForOres(config, player);
if (!canMineOres) {
this.warnToolInsufficient(config);
}
} }
private void drive(DigConfig config, PlayerEntity player) { this.drive(config, player, canMineOres);
}
/** 背包中没有能采掘目标矿物的工具时,每 5 秒提示一次。 */
private void warnToolInsufficient(DigConfig config) {
long now = System.currentTimeMillis();
if (now - this.lastToolWarn > 5000) {
this.lastToolWarn = now;
String ores = config.task == MineTask.NETHER ? config.netherOres : config.overworldOres;
this.sendChat("背包中没有能采掘 " + ores + " 的工具(如需要钻石镐),矿物绕道挖掘已暂停;放入合适工具后自动恢复");
}
}
private void drive(DigConfig config, PlayerEntity player, boolean canMineOres) {
switch (this.state) { switch (this.state) {
case IDLE -> { case IDLE -> {
// Baritone 未就绪时无法挖矿,节流提示并停留在空闲,避免静默无响应 // Baritone 未就绪时无法挖矿,节流提示并停留在空闲,避免静默无响应
@@ -156,7 +174,7 @@ public final class DigController {
} }
} }
case OVERWORLD -> { case OVERWORLD -> {
if (this.overworld.tick(config)) { if (this.overworld.tick(config, canMineOres)) {
this.bridge.cancelAll(); this.bridge.cancelAll();
if (this.overworld.isFull()) { if (this.overworld.isFull()) {
// 背包已满无法继续,直接结束任务,避免空转循环 // 背包已满无法继续,直接结束任务,避免空转循环
@@ -115,13 +115,11 @@ public final class AutoDigGui extends Screen {
y = this.checkPair(y, "护甲耐久保护", () -> this.config.armorDurabilityProtection, v -> this.config.armorDurabilityProtection = v, y = this.checkPair(y, "护甲耐久保护", () -> this.config.armorDurabilityProtection, v -> this.config.armorDurabilityProtection = v,
"工具耐久保护", () -> this.config.toolDurabilityProtection, v -> this.config.toolDurabilityProtection = v); "工具耐久保护", () -> this.config.toolDurabilityProtection, v -> this.config.toolDurabilityProtection = v);
y = this.checkPair(y, "自动换备用装备", () -> this.config.autoGearSwap, v -> this.config.autoGearSwap = v, y = this.checkPair(y, "自动换备用装备", () -> this.config.autoGearSwap, v -> this.config.autoGearSwap = v,
"自动插火把", () -> this.config.autoTorch, v -> this.config.autoTorch = v); "背包满丢垃圾", () -> this.config.autoTrash, v -> {
y = this.checkPair(y, "背包满丢垃圾", () -> this.config.autoTrash, v -> {
this.syncFields(); // 保留输入框中未应用的编辑 this.syncFields(); // 保留输入框中未应用的编辑
this.config.autoTrash = v; this.config.autoTrash = v;
this.init(); // 切换后显示/隐藏垃圾设置区 this.init(); // 切换后显示/隐藏垃圾设置区
}, });
null, null, null);
y += 10; y += 10;
y = this.header(y, "进食/保命/攻击阈值"); y = this.header(y, "进食/保命/攻击阈值");
@@ -157,7 +155,9 @@ public final class AutoDigGui extends Screen {
v -> this.config.overworldLevel = v, -64, 320); v -> this.config.overworldLevel = v, -64, 320);
y = this.numberRow(y, "巷道长度", () -> this.config.tunnelLength, y = this.numberRow(y, "巷道长度", () -> this.config.tunnelLength,
v -> this.config.tunnelLength = v, 4, 512); v -> this.config.tunnelLength = v, 4, 512);
y = this.fieldRow(y, "目标矿物(逗号分隔,如 diamond_ore,deepslate_diamond_ore)", y = this.numberRow(y, "支巷长度", () -> this.config.branchLength,
v -> this.config.branchLength = v, 1, 64);
y = this.fieldRow(y, "目标矿物(逗号分隔,带不带 minecraft: 前缀均可)",
this.config.overworldOres, tf -> this.overworldOresField = tf); this.config.overworldOres, tf -> this.overworldOresField = tf);
y += 10; y += 10;
} else if (this.config.task == MineTask.NETHER) { } else if (this.config.task == MineTask.NETHER) {
@@ -169,7 +169,7 @@ public final class AutoDigGui extends Screen {
null, null, null); null, null, null);
y = this.numberRow(y, "返程血量阈值", () -> this.config.netherFleeHealthThreshold, y = this.numberRow(y, "返程血量阈值", () -> this.config.netherFleeHealthThreshold,
v -> this.config.netherFleeHealthThreshold = v, 0, 100); v -> this.config.netherFleeHealthThreshold = v, 0, 100);
y = this.fieldRow(y, "目标矿物(逗号分隔,如 ancient_debris,nether_gold_ore)", y = this.fieldRow(y, "目标矿物(逗号分隔,带不带 minecraft: 前缀均可)",
this.config.netherOres, tf -> this.netherOresField = tf); this.config.netherOres, tf -> this.netherOresField = tf);
y += 10; y += 10;
} }
@@ -401,7 +401,6 @@ public final class AutoDigGui extends Screen {
target.avoidLava = this.config.avoidLava; target.avoidLava = this.config.avoidLava;
target.autoEat = this.config.autoEat; target.autoEat = this.config.autoEat;
target.eatHungerThreshold = this.config.eatHungerThreshold; target.eatHungerThreshold = this.config.eatHungerThreshold;
target.autoTorch = this.config.autoTorch;
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;
@@ -411,6 +410,7 @@ public final class AutoDigGui extends Screen {
target.netherAutoReturn = this.config.netherAutoReturn; target.netherAutoReturn = this.config.netherAutoReturn;
target.overworldLevel = this.config.overworldLevel; target.overworldLevel = this.config.overworldLevel;
target.tunnelLength = this.config.tunnelLength; target.tunnelLength = this.config.tunnelLength;
target.branchLength = this.config.branchLength;
target.overworldOres = this.config.overworldOres; target.overworldOres = this.config.overworldOres;
target.netherOres = this.config.netherOres; target.netherOres = this.config.netherOres;
target.netherTargetCount = this.config.netherTargetCount; target.netherTargetCount = this.config.netherTargetCount;
@@ -3,9 +3,15 @@ package autodig.client.miner;
import autodig.client.bridge.BaritoneBridge; import autodig.client.bridge.BaritoneBridge;
import autodig.client.config.DigConfig; import autodig.client.config.DigConfig;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
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.registry.Registries;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
/** /**
@@ -13,9 +19,13 @@ import net.minecraft.util.math.BlockPos;
* *
* <p>在指定的 Y 层,沿玩家朝向挖一条"主干隧道",并在主干上每隔 * <p>在指定的 Y 层,沿玩家朝向挖一条"主干隧道",并在主干上每隔
* {@code branchGap} 格向两侧各开一条侧支巷道,形成鱼骨状分布。 * {@code branchGap} 格向两侧各开一条侧支巷道,形成鱼骨状分布。
* 推进实现为:把鱼骨图案转成一串目标坐标 {@link GoalBlock},按顺序 * 推进实现为:把鱼骨图案转成一串目标坐标,按顺序派 Baritone 逐个前往;
* 派 Baritone 逐个前往;因为允许挖方块,Baritone 会走直线把挡路的方块 * 因为允许挖方块,Baritone 会走直线把挡路的方块挖掉,从而挖出巷道。
* 挖掉,从而挖出巷道。自动切换工具由 Baritone 的 {@code autoTool} 负责。</p> * 自动切换工具由 Baritone 的 {@code autoTool} 负责。</p>
*
* <p>智能找矿:推进巷道的同时,周期性扫描玩家周围(默认 10 格)的目标矿物;
* 发现后临时绕道前往该矿物将其挖掉,再回到巷道计划继续推进。
* 这样巷道壁旁暴露的钻石等目标矿不会漏挖。</p>
* *
* <p>遇到背包已满时提前结束,把控制权交还控制器(用于返回地表)。</p> * <p>遇到背包已满时提前结束,把控制权交还控制器(用于返回地表)。</p>
*/ */
@@ -37,9 +47,26 @@ public final class OverworldMiner {
private long lastLog; private long lastLog;
/** 背包满连续持续的 tick 数(去抖:给自动丢垃圾留出腾位时间) */ /** 背包满连续持续的 tick 数(去抖:给自动丢垃圾留出腾位时间) */
private int fullTicks; private int fullTicks;
/** 矿物绕道目标;非 null 时优先前往挖掘该矿物 */
private BlockPos detourTarget;
/** 绕道已持续的 tick 数(超时放弃,避免卡死) */
private int detourTicks;
/** 上一次扫描附近矿物的时间戳(节流) */
private long lastOreScan;
/** 上次放弃的绕道点与时间(同一点短时间内不再重试,避免卡在不可达矿上) */
private BlockPos abandonedTarget;
private long abandonedAt;
/** 背包满去抖阈值(tick):连续这么久都满才认定为真满 */ /** 背包满去抖阈值(tick):连续这么久都满才认定为真满 */
private static final int FULL_DEBOUNCE_TICKS = 20; private static final int FULL_DEBOUNCE_TICKS = 20;
/** 矿物扫描间隔(毫秒) */
private static final long ORE_SCAN_INTERVAL_MS = 1000;
/** 矿物扫描半径(方块) */
private static final int ORE_SCAN_RADIUS = 10;
/** 单次绕道超时(tick):超过则放弃该矿点回到巷道计划 */
private static final int DETOUR_TIMEOUT_TICKS = 400;
/** 同一矿点放弃后的重试冷却(毫秒) */
private static final long ABANDON_COOLDOWN_MS = 30_000;
public OverworldMiner(BaritoneBridge bridge) { public OverworldMiner(BaritoneBridge bridge) {
this.bridge = bridge; this.bridge = bridge;
@@ -59,7 +86,7 @@ public final class OverworldMiner {
this.yLevel = config.overworldLevel; 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 = Math.max(1, config.branchLength);
int branchGap = 8; int branchGap = 8;
// 用玩家水平朝向(而非移动方向)确定挖矿方向,站着不动时也稳定 // 用玩家水平朝向(而非移动方向)确定挖矿方向,站着不动时也稳定
net.minecraft.util.math.Direction facing = player.getHorizontalFacing(); net.minecraft.util.math.Direction facing = player.getHorizontalFacing();
@@ -70,6 +97,10 @@ public final class OverworldMiner {
this.done = false; this.done = false;
this.full = false; this.full = false;
this.fullTicks = 0; this.fullTicks = 0;
this.detourTarget = null;
this.detourTicks = 0;
this.abandonedTarget = null;
this.abandonedAt = 0;
} }
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) {
@@ -101,8 +132,9 @@ public final class OverworldMiner {
* 每帧推进一次。背包已满或走完全程则返回已完成。 * 每帧推进一次。背包已满或走完全程则返回已完成。
* *
* @param config 挖矿配置 * @param config 挖矿配置
* @param canMineOres 手持工具是否足以挖目标矿物(false 时跳过矿物绕道,避免白挖不掉落)
*/ */
public boolean tick(DigConfig config) { public boolean tick(DigConfig config, boolean canMineOres) {
if (this.done) { if (this.done) {
return true; return true;
} }
@@ -128,6 +160,12 @@ public final class OverworldMiner {
System.out.println("[AutoDig] overworld 结束:计划完成 step=" + this.step + " size=" + this.plan.size()); System.out.println("[AutoDig] overworld 结束:计划完成 step=" + this.step + " size=" + this.plan.size());
return true; return true;
} }
// ---- 矿物绕道挖掘:优先把附近暴露的目标矿物挖掉 ----
if (this.handleOreDetour(config, client, player, canMineOres)) {
return false; // 绕道期间不推进巷道
}
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(); long now = System.currentTimeMillis();
@@ -152,6 +190,101 @@ public final class OverworldMiner {
return false; return false;
} }
/**
* 处理矿物绕道挖掘。
*
* @return true 表示当前正在绕道挖矿(调用方应暂停巷道推进)
*/
private boolean handleOreDetour(DigConfig config, MinecraftClient client, PlayerEntity player, boolean canMineOres) {
long now = System.currentTimeMillis();
if (this.detourTarget != null) {
BlockState state = client.world.getBlockState(this.detourTarget);
if (!this.isTargetOre(config, state)) {
// 已挖掉或目标失效:回到巷道计划
this.detourTarget = null;
return false;
}
this.detourTicks++;
if (this.detourTicks > DETOUR_TIMEOUT_TICKS) {
// 太久没挖到(不可达等):放弃该点并冷却
System.out.println("[AutoDig] overworld 绕道超时,放弃矿点: " + this.detourTarget);
this.abandonedTarget = this.detourTarget;
this.abandonedAt = now;
this.detourTarget = null;
return false;
}
if (now - this.lastIssue > 250) {
this.bridge.gotoBlock(this.detourTarget);
this.lastIssue = now;
}
return true;
}
// 节流扫描附近目标矿物
if (!canMineOres || now - this.lastOreScan < ORE_SCAN_INTERVAL_MS) {
return false;
}
this.lastOreScan = now;
BlockPos ore = this.findNearbyOre(config, client, player);
if (ore != null) {
this.detourTarget = ore;
this.detourTicks = 0;
this.bridge.gotoBlock(ore);
this.lastIssue = now;
System.out.println("[AutoDig] overworld 发现目标矿物,绕道挖掘: " + ore);
return true;
}
return false;
}
/** 在玩家周围扫描最近的目标矿物方块。 */
private BlockPos findNearbyOre(DigConfig config, MinecraftClient client, PlayerEntity player) {
Set<String> targets = this.targetOrePaths(config);
if (targets.isEmpty()) {
return null;
}
BlockPos best = null;
double bestDist = Double.MAX_VALUE;
for (BlockPos pos : BlockPos.iterateOutwards(player.getBlockPos(),
ORE_SCAN_RADIUS, ORE_SCAN_RADIUS, ORE_SCAN_RADIUS)) {
String path = Registries.BLOCK.getId(client.world.getBlockState(pos).getBlock()).getPath();
if (!targets.contains(path)) {
continue;
}
// 最近放弃过的矿点在冷却期内不再尝试
if (pos.equals(this.abandonedTarget)
&& System.currentTimeMillis() - this.abandonedAt < ABANDON_COOLDOWN_MS) {
continue;
}
double d = pos.getSquaredDistance(player.getBlockPos());
if (d < bestDist) {
bestDist = d;
best = pos.toImmutable();
}
}
return best;
}
/** 判断方块状态是否为配置中的目标矿物(按 path 匹配,兼容命名空间前缀)。 */
private boolean isTargetOre(DigConfig config, BlockState state) {
if (state.isOf(Blocks.AIR)) {
return false;
}
String path = Registries.BLOCK.getId(state.getBlock()).getPath();
return this.targetOrePaths(config).contains(path);
}
/** 目标矿物 path 集合(去命名空间,兼容 "minecraft:" 前缀写法)。 */
private Set<String> targetOrePaths(DigConfig config) {
Set<String> set = new HashSet<>();
for (String ore : config.getOverworldOreList()) {
String path = ore.contains(":") ? ore.substring(ore.indexOf(':') + 1) : ore;
if (!path.isBlank()) {
set.add(path);
}
}
return set;
}
private double distanceSq(PlayerEntity player, BlockPos target) { private double distanceSq(PlayerEntity player, BlockPos target) {
double dx = player.getX() - (target.getX() + 0.5); double dx = player.getX() - (target.getX() + 0.5);
double dy = player.getY() - target.getY(); double dy = player.getY() - target.getY();
@@ -218,14 +351,4 @@ public final class OverworldMiner {
this.full = false; this.full = false;
this.fullTicks = 0; this.fullTicks = 0;
} }
/**
* 派发矿脉挖掘指令(用于在鱼骨巷道推进过程中遇到目标矿物时挖掉)。
* 由控制器在合适时机调用。
*/
public void mineOres(DigConfig config) {
if (!config.getOverworldOreList().isEmpty()) {
this.bridge.mineByName(-1, config.getOverworldOreList().toArray(String[]::new));
}
}
} }
@@ -1,6 +1,11 @@
package autodig.client.service; package autodig.client.service;
import autodig.client.config.DigConfig; import autodig.client.config.DigConfig;
import autodig.client.config.MineTask;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerInteractionManager; import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.EquipmentSlot;
@@ -14,8 +19,10 @@ import net.minecraft.item.ItemStack;
import net.minecraft.item.PickaxeItem; import net.minecraft.item.PickaxeItem;
import net.minecraft.item.ShovelItem; import net.minecraft.item.ShovelItem;
import net.minecraft.item.SwordItem; import net.minecraft.item.SwordItem;
import net.minecraft.registry.Registries;
import net.minecraft.screen.ScreenHandler; import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.slot.SlotActionType; import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.util.Identifier;
/** /**
* 装备自动更换服务。 * 装备自动更换服务。
@@ -26,13 +33,21 @@ import net.minecraft.screen.slot.SlotActionType;
* 新装备就位。交换走正规网络包(clickSlot),客户端/服务端自动同步, * 新装备就位。交换走正规网络包(clickSlot),客户端/服务端自动同步,
* 单人/多人均可用。找不到备用装备时返回 false,由控制器走"暂停保装备"或 * 单人/多人均可用。找不到备用装备时返回 false,由控制器走"暂停保装备"或
* "下界返程"逻辑。</p> * "下界返程"逻辑。</p>
*
* <p>工具适用性检查({@link #ensureToolForOres}):挖掘前校验手持工具能否
* 采掘目标矿物(如远古残骸必须钻石镐以上,铁镐只会白挖不掉落),不适用时
* 自动从背包换上能采掘且剩余耐久最高的工具。</p>
*/ */
public final class AutoGear { public final class AutoGear {
/** 每次换装后的冷却 tick 数:等待客户端预测/服务端确认生效,避免连续误判 */ /** 每次换装后的冷却 tick 数:等待客户端预测/服务端确认生效,避免连续误判 */
private static final int SWAP_COOLDOWN_TICKS = 10; private static final int SWAP_COOLDOWN_TICKS = 10;
/** 工具适用性检查间隔(毫秒),避免每帧解析方块注册表 */
private static final long TOOL_CHECK_INTERVAL_MS = 1000;
private int coolTicks; private int coolTicks;
private long lastToolCheck;
private boolean lastToolOk = true;
/** 每帧推进冷却。 */ /** 每帧推进冷却。 */
public void tick() { public void tick() {
@@ -93,6 +108,102 @@ public final class AutoGear {
return false; return false;
} }
/**
* 确保手持工具能采掘目标矿物(如远古残骸必须钻石镐以上)。
*
* <p>每秒检查一次:把目标矿物 id 解析成 Block(兼容 "minecraft:" 前缀),
* 用 {@link ItemStack#isSuitableFor(BlockState)} 校验手持工具;不适用时
* 从背包里找能采掘该矿物、剩余耐久最高的工具换上。全部换过仍不适用,
* 或配置的矿物 id 无法解析,返回 false。</p>
*
* @param config 挖矿配置
* @param player 玩家
* @return true 表示手持工具适用(或无需工具),false 表示没有合适工具
*/
public boolean ensureToolForOres(DigConfig config, PlayerEntity player) {
long now = System.currentTimeMillis();
if (now - this.lastToolCheck < TOOL_CHECK_INTERVAL_MS) {
return this.lastToolOk;
}
this.lastToolCheck = now;
this.lastToolOk = true;
MinecraftClient client = MinecraftClient.getInstance();
if (client.interactionManager == null) {
return true;
}
List<String> ores = config.task == MineTask.NETHER
? config.getNetherOreList() : config.getOverworldOreList();
if (ores.isEmpty()) {
return true;
}
// 收集所有目标矿物方块状态(解析失败的 id 直接判定无合适工具,提示用户修正)
List<BlockState> oreStates = new java.util.ArrayList<>();
for (String ore : ores) {
Identifier id = Identifier.tryParse(ore.contains(":") ? ore : "minecraft:" + ore);
if (id == null) {
continue;
}
Block block = Registries.BLOCK.get(id);
if (block != null && block != Blocks.AIR) {
oreStates.add(block.getDefaultState());
}
}
if (oreStates.isEmpty()) {
return true; // 无法解析时不拦挖矿(Baritone mineByName 自行处理未知 id)
}
ItemStack held = player.getMainHandStack();
if (isSuitableForAll(held, oreStates)) {
return true;
}
// 手持工具(或空手)不适用:从背包找能采掘全部目标矿物的工具中耐久最高的换上
PlayerInventory inv = player.getInventory();
ItemStack best = null;
for (int i = 0; i < 36; i++) {
if (i == inv.selectedSlot) {
continue;
}
ItemStack s = inv.main.get(i);
if (s.isEmpty() || !isTool(s) || !s.isDamageable()) {
continue;
}
if (isSuitableForAll(s, oreStates)
&& (best == null || remaining(s) > remaining(best))) {
best = s;
}
}
if (best == null) {
this.lastToolOk = false;
return false;
}
if (player.currentScreenHandler != player.playerScreenHandler
|| !player.currentScreenHandler.getCursorStack().isEmpty()) {
// 界面占用中无法换装,本秒内视为不适用,下一秒重试
this.lastToolOk = false;
return false;
}
if (this.swap(player, held, best)) {
this.coolTicks = SWAP_COOLDOWN_TICKS;
System.out.println("[AutoDig] 已切换适用工具: " + Registries.ITEM.getId(best.getItem())
+ "(原手持 " + Registries.ITEM.getId(held.getItem()) + " 无法采掘目标矿物)");
}
this.lastToolOk = isSuitableForAll(player.getMainHandStack(), oreStates);
return this.lastToolOk;
}
/** 该物品堆是否非空且能采掘全部目标矿物方块。 */
private static boolean isSuitableForAll(ItemStack stack, List<BlockState> states) {
if (stack.isEmpty()) {
return false;
}
for (BlockState state : states) {
if (!stack.isSuitableFor(state)) {
return false;
}
}
return true;
}
/** 手持工具的替换候选:同类别中剩余耐久最高且高于当前手持的。 */ /** 手持工具的替换候选:同类别中剩余耐久最高且高于当前手持的。 */
private ItemStack findToolReplacement(PlayerInventory inv, ItemStack held) { private ItemStack findToolReplacement(PlayerInventory inv, ItemStack held) {
String kind = toolKind(held.getItem()); String kind = toolKind(held.getItem());
@@ -1,116 +0,0 @@
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);
}
}
+1 -1
查看文件
@@ -3,7 +3,7 @@
"id": "autodig", "id": "autodig",
"version": "${version}", "version": "${version}",
"name": "AutoDig", "name": "AutoDig",
"description": "自动挖矿模组(内置 Baritone 1.13.1,无需另行安装):主世界模式与下界模式,均可自定义目标矿物。主世界鱼骨巷道挖矿,下界自动进出传送门挖矿并支持返程。支持自动切换工具、自动进食(避开特殊食物)、自动插火把防刷怪、杀戮光环(不误伤中立生物)、保命优先、优先绕开岩浆、装备耐久保护(自动换备用装备)、背包近满提前丢垃圾防误判返程、死亡重生回点、实时状态 HUD。", "description": "自动挖矿模组(内置 Baritone 1.13.1,无需另行安装):主世界模式与下界模式,均可自定义目标矿物(带不带 minecraft: 前缀均可)。主世界鱼骨巷道挖矿 + 附近目标矿物自动绕道挖掘;下界自动进出传送门挖矿并支持返程。自动切换最适用工具(远古残骸等自动换钻石镐)、自动进食(避开特殊食物)、杀戮光环(不误伤中立生物)、保命优先、优先绕开岩浆、装备耐久保护(自动换备用装备)、背包近满提前丢垃圾防误判返程、死亡重生回点、实时状态 HUD。",
"authors": [], "authors": [],
"contact": {}, "contact": {},
"license": "All-Rights-Reserved", "license": "All-Rights-Reserved",