首次提交 AutoDig Mod
这个提交包含在:
@@ -0,0 +1,235 @@
|
||||
package autodig.client.controller;
|
||||
|
||||
import autodig.client.bridge.BaritoneBridge;
|
||||
import autodig.client.config.DigConfig;
|
||||
import autodig.client.config.MineTask;
|
||||
import autodig.client.miner.DebrisHandler;
|
||||
import autodig.client.miner.FishboneMiner;
|
||||
import autodig.client.service.AutoEat;
|
||||
import autodig.client.service.DeathRecorder;
|
||||
import autodig.client.service.KillAura;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.Heightmap;
|
||||
|
||||
/**
|
||||
* 挖矿总控制器:把配置、Baritone 桥接、自动进食、杀戮光环、死亡记录和各任务
|
||||
* 专用挖矿器串起来,驱动一个简单的状态机。
|
||||
*/
|
||||
public final class DigController {
|
||||
|
||||
/** 内部状态机 */
|
||||
private enum State {
|
||||
IDLE, FISHBONE, CLIMB, DEBRIS, FLEE, RETURN_TO_DEATH
|
||||
}
|
||||
|
||||
private final BaritoneBridge bridge = new BaritoneBridge();
|
||||
private final AutoEat autoEat = new AutoEat();
|
||||
private final KillAura killAura = new KillAura();
|
||||
private final DeathRecorder deathRecorder = new DeathRecorder();
|
||||
private final FishboneMiner fishbone = new FishboneMiner(this.bridge);
|
||||
private final DebrisHandler debris = new DebrisHandler(this.bridge);
|
||||
|
||||
private State state = State.IDLE;
|
||||
private BlockPos resumeTarget;
|
||||
private long lastIssue;
|
||||
private boolean wasDead;
|
||||
|
||||
/** 每帧驱动整个自动挖矿流程。 */
|
||||
public void tick(DigConfig config) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.player == null || client.world == null) {
|
||||
this.state = State.IDLE;
|
||||
return;
|
||||
}
|
||||
PlayerEntity player = client.player;
|
||||
|
||||
// 常驻能力:杀戮光环、自动进食
|
||||
this.killAura.tick(config);
|
||||
this.autoEat.tick(config);
|
||||
|
||||
// 死亡处理:死亡瞬间暂停任务,重生后按需回到死亡坐标
|
||||
this.deathRecorder.tick(config, this::onRespawn);
|
||||
if (player.isDead()) {
|
||||
this.wasDead = true;
|
||||
this.bridge.cancelAll();
|
||||
return;
|
||||
}
|
||||
this.wasDead = false;
|
||||
|
||||
// 保命优先:血线过低时向上撤离(不冒进挖矿)
|
||||
if (config.survivalFirst && this.healthPercent(player) < config.healthFleeThreshold) {
|
||||
this.fleeTick(config);
|
||||
return;
|
||||
}
|
||||
|
||||
// 未选择任务时归位
|
||||
if (config.task == MineTask.NONE) {
|
||||
if (this.state != State.IDLE) {
|
||||
this.bridge.cancelAll();
|
||||
this.state = State.IDLE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.drive(config, player);
|
||||
}
|
||||
|
||||
private void drive(DigConfig config, PlayerEntity player) {
|
||||
switch (this.state) {
|
||||
case IDLE -> {
|
||||
if (config.task == MineTask.DIAMOND) {
|
||||
this.fishbone.init(config);
|
||||
this.state = State.FISHBONE;
|
||||
} else if (config.task == MineTask.ANCIENT_DEBRIS) {
|
||||
this.debris.reset();
|
||||
this.state = State.DEBRIS;
|
||||
}
|
||||
}
|
||||
case FISHBONE -> {
|
||||
player.sendMessage(Text.literal("AutoDig: 主世界鱼骨挖矿进行中"), true);
|
||||
if (this.fishbone.tick(config)) {
|
||||
this.bridge.cancelAll();
|
||||
this.pathToSurface(player.getBlockPos());
|
||||
this.state = State.CLIMB;
|
||||
}
|
||||
}
|
||||
case CLIMB -> {
|
||||
if (this.nearSurface(player)) {
|
||||
this.finish("鱼骨挖矿完成,已返回地表");
|
||||
}
|
||||
}
|
||||
case DEBRIS -> {
|
||||
player.sendMessage(Text.literal("AutoDig: 下界远古残骸挖矿进行中"), true);
|
||||
if (this.debris.tick(config)) {
|
||||
this.finish("远古残骸任务完成,已回到主世界");
|
||||
}
|
||||
}
|
||||
case RETURN_TO_DEATH -> this.returnToDeathTick(player, config);
|
||||
case FLEE -> {
|
||||
// FLEE 在 tick() 中单独处理,正常情况下不会进到这里
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 重生后先回到最近的死亡坐标,再续上原任务。 */
|
||||
private void onRespawn() {
|
||||
BlockPos deathPos = this.deathRecorder.lastDeathPos();
|
||||
if (deathPos != null) {
|
||||
this.bridge.cancelAll();
|
||||
this.resumeTarget = deathPos;
|
||||
this.state = State.RETURN_TO_DEATH;
|
||||
}
|
||||
}
|
||||
|
||||
private void returnToDeathTick(PlayerEntity player, DigConfig config) {
|
||||
if (this.resumeTarget == null) {
|
||||
this.resumeTask(config);
|
||||
return;
|
||||
}
|
||||
double dist = this.distanceSq(player, this.resumeTarget);
|
||||
if (dist < 9.0) {
|
||||
this.resumeTarget = null;
|
||||
this.resumeTask(config);
|
||||
} else {
|
||||
this.issue(() -> this.bridge.gotoBlock(this.resumeTarget));
|
||||
}
|
||||
}
|
||||
|
||||
/** 按当前任务与所在维度恢复挖矿。 */
|
||||
private void resumeTask(DigConfig config) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
boolean inNether = client.world != null && client.world.getRegistryKey() == net.minecraft.world.World.NETHER;
|
||||
if (config.task == MineTask.DIAMOND) {
|
||||
this.fishbone.init(config);
|
||||
this.state = State.FISHBONE;
|
||||
} else if (config.task == MineTask.ANCIENT_DEBRIS) {
|
||||
this.debris.reset();
|
||||
this.state = State.DEBRIS;
|
||||
// 若已在下界,让残留处理机直接从挖矿阶段继续
|
||||
if (inNether) {
|
||||
this.state = State.DEBRIS;
|
||||
}
|
||||
} else {
|
||||
this.state = State.IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
/** 保命撤离:把玩家送到脚下地表,血线恢复后继续原任务。 */
|
||||
private void fleeTick(DigConfig config) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
PlayerEntity player = client.player;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
this.bridge.cancelAll();
|
||||
this.pathToSurface(player.getBlockPos());
|
||||
if (this.healthPercent(player) >= config.healthFleeThreshold + 20) {
|
||||
this.resumeTask(config);
|
||||
}
|
||||
}
|
||||
|
||||
private void finish(String message) {
|
||||
this.bridge.cancelAll();
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.player != null) {
|
||||
client.player.sendMessage(Text.literal("AutoDig: " + message), true);
|
||||
}
|
||||
this.state = State.IDLE;
|
||||
}
|
||||
|
||||
private double healthPercent(PlayerEntity player) {
|
||||
return player.getHealth() / Math.max(player.getMaxHealth(), 1.0) * 100.0;
|
||||
}
|
||||
|
||||
private double distanceSq(PlayerEntity player, BlockPos target) {
|
||||
double dx = player.getX() - (target.getX() + 0.5);
|
||||
double dz = player.getZ() - (target.getZ() + 0.5);
|
||||
return dx * dx + dz * dz;
|
||||
}
|
||||
|
||||
private void issue(Runnable action) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - this.lastIssue > 250) {
|
||||
action.run();
|
||||
this.lastIssue = now;
|
||||
}
|
||||
}
|
||||
|
||||
/** 计算垂直方向上的地表坐标并派发前往(节流)。 */
|
||||
private void pathToSurface(BlockPos below) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.world == null) {
|
||||
return;
|
||||
}
|
||||
int x = below.getX();
|
||||
int z = below.getZ();
|
||||
int top = client.world.getTopY(Heightmap.Type.MOTION_BLOCKING, x, z);
|
||||
BlockPos surface = new BlockPos(x, Math.max(top, below.getY()), z);
|
||||
this.issue(() -> this.bridge.gotoBlock(surface));
|
||||
}
|
||||
|
||||
/** 玩家是否已回到地表。 */
|
||||
private boolean nearSurface(PlayerEntity player) {
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
if (client.world == null) {
|
||||
return true;
|
||||
}
|
||||
int x = player.getBlockX();
|
||||
int z = player.getBlockZ();
|
||||
int top = client.world.getTopY(Heightmap.Type.MOTION_BLOCKING, x, z);
|
||||
return Math.abs(player.getY() - (top + 1)) < 4.0;
|
||||
}
|
||||
|
||||
/** @return Baritone 桥接(供其他模块复用)。 */
|
||||
public BaritoneBridge bridge() {
|
||||
return this.bridge;
|
||||
}
|
||||
|
||||
/** 供 GUI/调试获取当前状态文本。 */
|
||||
public String statusText() {
|
||||
return this.state.name();
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户