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 行删除
@@ -77,6 +77,7 @@ public final class ConfigStore {
}
c.overworldLevel = clamp(c.overworldLevel, -64, 320);
c.tunnelLength = clamp(c.tunnelLength, 4, 512);
c.branchLength = clamp(c.branchLength, 1, 64);
c.netherTargetCount = clamp(c.netherTargetCount, -1, 512);
if (c.overworldOres == null || c.overworldOres.isBlank()) {
c.overworldOres = "diamond_ore,deepslate_diamond_ore";
@@ -18,8 +18,6 @@ public class DigConfig {
public boolean autoEat = true;
/** 饥饿度低于该值时开始进食(20 为满值) */
public int eatHungerThreshold = 8;
/** 自动插火把(隧道黑暗时自动从背包取火把放置,防止刷怪) */
public boolean autoTorch = true;
// ---- 保命/安全 ----
/** 保命优先:生命过低时暂停挖矿并向上撤离 */
@@ -66,7 +64,9 @@ public class DigConfig {
public int overworldLevel = -58;
/** 鱼骨主干巷道长度(方块) */
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";
// ---- 下界模式参数 ----
@@ -114,7 +114,6 @@ 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,7 +7,6 @@ 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;
@@ -36,7 +35,6 @@ 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);
@@ -46,6 +44,8 @@ public final class DigController {
private BlockPos resumeTarget;
private long lastIssue;
private long lastWarn;
/** 上一次"没有合适工具"提示的时间戳(节流) */
private long lastToolWarn;
/** 返回死亡点的截止时间(超时后直接续任务,避免不可达时死循环) */
private long returnDeadline;
/** 装备耐久保护触发前所处的状态,用于恢复后回到该状态 */
@@ -60,11 +60,10 @@ 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();
// 死亡处理:记录死亡次数,暂停任务,重生后按需回到死亡坐标
@@ -133,10 +132,29 @@ public final class DigController {
return;
}
this.drive(config, player);
// 工具适用性保障:确保手持工具能采掘目标矿物(如远古残骸必须钻石镐)
boolean canMineOres = true;
if (config.autoTool) {
canMineOres = this.autoGear.ensureToolForOres(config, player);
if (!canMineOres) {
this.warnToolInsufficient(config);
}
}
this.drive(config, player, canMineOres);
}
private void drive(DigConfig config, PlayerEntity player) {
/** 背包中没有能采掘目标矿物的工具时,每 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) {
case IDLE -> {
// Baritone 未就绪时无法挖矿,节流提示并停留在空闲,避免静默无响应
@@ -156,7 +174,7 @@ public final class DigController {
}
}
case OVERWORLD -> {
if (this.overworld.tick(config)) {
if (this.overworld.tick(config, canMineOres)) {
this.bridge.cancelAll();
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,
"工具耐久保护", () -> this.config.toolDurabilityProtection, v -> this.config.toolDurabilityProtection = v);
y = this.checkPair(y, "自动换备用装备", () -> this.config.autoGearSwap, v -> this.config.autoGearSwap = v,
"自动插火把", () -> this.config.autoTorch, v -> this.config.autoTorch = v);
y = this.checkPair(y, "背包满丢垃圾", () -> this.config.autoTrash, v -> {
"背包满丢垃圾", () -> this.config.autoTrash, v -> {
this.syncFields(); // 保留输入框中未应用的编辑
this.config.autoTrash = v;
this.init(); // 切换后显示/隐藏垃圾设置区
},
null, null, null);
});
y += 10;
y = this.header(y, "进食/保命/攻击阈值");
@@ -157,7 +155,9 @@ public final class AutoDigGui extends Screen {
v -> this.config.overworldLevel = v, -64, 320);
y = this.numberRow(y, "巷道长度", () -> this.config.tunnelLength,
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);
y += 10;
} else if (this.config.task == MineTask.NETHER) {
@@ -169,7 +169,7 @@ public final class AutoDigGui extends Screen {
null, null, null);
y = this.numberRow(y, "返程血量阈值", () -> this.config.netherFleeHealthThreshold,
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);
y += 10;
}
@@ -401,7 +401,6 @@ public final class AutoDigGui extends Screen {
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;
@@ -411,6 +410,7 @@ public final class AutoDigGui extends Screen {
target.netherAutoReturn = this.config.netherAutoReturn;
target.overworldLevel = this.config.overworldLevel;
target.tunnelLength = this.config.tunnelLength;
target.branchLength = this.config.branchLength;
target.overworldOres = this.config.overworldOres;
target.netherOres = this.config.netherOres;
target.netherTargetCount = this.config.netherTargetCount;
@@ -3,9 +3,15 @@ package autodig.client.miner;
import autodig.client.bridge.BaritoneBridge;
import autodig.client.config.DigConfig;
import java.util.ArrayList;
import java.util.HashSet;
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.entity.player.PlayerEntity;
import net.minecraft.registry.Registries;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
/**
@@ -13,9 +19,13 @@ import net.minecraft.util.math.BlockPos;
*
* <p>在指定的 Y 层,沿玩家朝向挖一条"主干隧道",并在主干上每隔
* {@code branchGap} 格向两侧各开一条侧支巷道,形成鱼骨状分布。
* 推进实现为:把鱼骨图案转成一串目标坐标 {@link GoalBlock},按顺序
* 派 Baritone 逐个前往;因为允许挖方块,Baritone 会走直线把挡路的方块
* 挖掉,从而挖出巷道。自动切换工具由 Baritone 的 {@code autoTool} 负责。</p>
* 推进实现为:把鱼骨图案转成一串目标坐标,按顺序派 Baritone 逐个前往;
* 因为允许挖方块,Baritone 会走直线把挡路的方块挖掉,从而挖出巷道。
* 自动切换工具由 Baritone 的 {@code autoTool} 负责。</p>
*
* <p>智能找矿:推进巷道的同时,周期性扫描玩家周围(默认 10 格)的目标矿物;
* 发现后临时绕道前往该矿物将其挖掉,再回到巷道计划继续推进。
* 这样巷道壁旁暴露的钻石等目标矿不会漏挖。</p>
*
* <p>遇到背包已满时提前结束,把控制权交还控制器(用于返回地表)。</p>
*/
@@ -37,9 +47,26 @@ public final class OverworldMiner {
private long lastLog;
/** 背包满连续持续的 tick 数(去抖:给自动丢垃圾留出腾位时间) */
private int fullTicks;
/** 矿物绕道目标;非 null 时优先前往挖掘该矿物 */
private BlockPos detourTarget;
/** 绕道已持续的 tick 数(超时放弃,避免卡死) */
private int detourTicks;
/** 上一次扫描附近矿物的时间戳(节流) */
private long lastOreScan;
/** 上次放弃的绕道点与时间(同一点短时间内不再重试,避免卡在不可达矿上) */
private BlockPos abandonedTarget;
private long abandonedAt;
/** 背包满去抖阈值(tick):连续这么久都满才认定为真满 */
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) {
this.bridge = bridge;
@@ -59,7 +86,7 @@ public final class OverworldMiner {
this.yLevel = config.overworldLevel;
BlockPos stop = player.getBlockPos();
this.start = new BlockPos(stop.getX(), this.yLevel, stop.getZ());
int branchLen = 6;
int branchLen = Math.max(1, config.branchLength);
int branchGap = 8;
// 用玩家水平朝向(而非移动方向)确定挖矿方向,站着不动时也稳定
net.minecraft.util.math.Direction facing = player.getHorizontalFacing();
@@ -70,6 +97,10 @@ public final class OverworldMiner {
this.done = false;
this.full = false;
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) {
@@ -100,9 +131,10 @@ 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) {
return true;
}
@@ -128,6 +160,12 @@ public final class OverworldMiner {
System.out.println("[AutoDig] overworld 结束:计划完成 step=" + this.step + " size=" + this.plan.size());
return true;
}
// ---- 矿物绕道挖掘:优先把附近暴露的目标矿物挖掉 ----
if (this.handleOreDetour(config, client, player, canMineOres)) {
return false; // 绕道期间不推进巷道
}
BlockPos target = this.plan.get(this.step);
double dist = this.distanceSq(player, target);
long now = System.currentTimeMillis();
@@ -152,6 +190,101 @@ public final class OverworldMiner {
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) {
double dx = player.getX() - (target.getX() + 0.5);
double dy = player.getY() - target.getY();
@@ -218,14 +351,4 @@ public final class OverworldMiner {
this.full = false;
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;
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.network.ClientPlayerInteractionManager;
import net.minecraft.entity.EquipmentSlot;
@@ -14,8 +19,10 @@ import net.minecraft.item.ItemStack;
import net.minecraft.item.PickaxeItem;
import net.minecraft.item.ShovelItem;
import net.minecraft.item.SwordItem;
import net.minecraft.registry.Registries;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.util.Identifier;
/**
* 装备自动更换服务。
@@ -26,13 +33,21 @@ import net.minecraft.screen.slot.SlotActionType;
* 新装备就位。交换走正规网络包(clickSlot),客户端/服务端自动同步,
* 单人/多人均可用。找不到备用装备时返回 false,由控制器走"暂停保装备"或
* "下界返程"逻辑。</p>
*
* <p>工具适用性检查({@link #ensureToolForOres}):挖掘前校验手持工具能否
* 采掘目标矿物(如远古残骸必须钻石镐以上,铁镐只会白挖不掉落),不适用时
* 自动从背包换上能采掘且剩余耐久最高的工具。</p>
*/
public final class AutoGear {
/** 每次换装后的冷却 tick 数:等待客户端预测/服务端确认生效,避免连续误判 */
private static final int SWAP_COOLDOWN_TICKS = 10;
/** 工具适用性检查间隔(毫秒),避免每帧解析方块注册表 */
private static final long TOOL_CHECK_INTERVAL_MS = 1000;
private int coolTicks;
private long lastToolCheck;
private boolean lastToolOk = true;
/** 每帧推进冷却。 */
public void tick() {
@@ -93,6 +108,102 @@ public final class AutoGear {
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) {
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",
"version": "${version}",
"name": "AutoDig",
"description": "自动挖矿模组(内置 Baritone 1.13.1,无需另行安装):主世界模式与下界模式,均可自定义目标矿物。主世界鱼骨巷道挖矿,下界自动进出传送门挖矿并支持返程。支持自动切换工具、自动进食(避开特殊食物)、自动插火把防刷怪、杀戮光环(不误伤中立生物)、保命优先、优先绕开岩浆、装备耐久保护(自动换备用装备)、背包近满提前丢垃圾防误判返程、死亡重生回点、实时状态 HUD。",
"description": "自动挖矿模组(内置 Baritone 1.13.1,无需另行安装):主世界模式与下界模式,均可自定义目标矿物(带不带 minecraft: 前缀均可)。主世界鱼骨巷道挖矿 + 附近目标矿物自动绕道挖掘;下界自动进出传送门挖矿并支持返程。自动切换最适用工具(远古残骸等自动换钻石镐)、自动进食(避开特殊食物)、杀戮光环(不误伤中立生物)、保命优先、优先绕开岩浆、装备耐久保护(自动换备用装备)、背包近满提前丢垃圾防误判返程、死亡重生回点、实时状态 HUD。",
"authors": [],
"contact": {},
"license": "All-Rights-Reserved",