feat: 电气化间隔放置+自动走近起点+材料预案+避险进食

这个提交包含在:
lyizai
2026-09-03 21:42:17 +08:00
父节点 d8d7723110
当前提交 3bd9a41d63
共修改 15 个文件,包含 1443 行新增和 87 行删除
@@ -0,0 +1,215 @@
package com.autorail.pathing;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.registry.tag.FluidTags;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
/**
* 自动走到远处的起始点:内置 A* 步行寻路(4 向 + 跳 1 格 + 落 3 格 + 可涉水)。
*
* 与铺轨寻路解耦:先走过去,再从起点开始铺轨;走不到则明确报错让用户手动接近。
*/
public class WalkPathfinder {
public enum State { RUNNING, FOUND, FAILED }
private static final int MARGIN = 32;
private static final long MAX_EXPANSIONS = 200_000L;
private static final float COST_JUMP = 1.7f;
private static final float COST_WATER = 0.8f;
private final ClientWorld world;
private final BlockPos start;
private final BlockPos goal;
private final PriorityQueue<Node> open = new PriorityQueue<>(Comparator.comparingDouble(n -> n.f));
private final Map<Long, Node> best = new HashMap<>();
private final List<BlockPos> result = new ArrayList<>();
private State state = State.RUNNING;
private String failKey;
private long expansions;
private int opened;
private int minX, minY, minZ, maxX, maxY, maxZ;
public WalkPathfinder(ClientWorld world, BlockPos start, BlockPos goal) {
this.world = world;
this.start = start.toImmutable();
this.goal = goal.toImmutable();
computeRegion();
Node s = new Node(this.start, null, 0f, h(this.start));
best.put(this.start.asLong(), s);
open.add(s);
opened++;
}
public State getState() {
return state;
}
public String getFailKey() {
return failKey;
}
public List<BlockPos> getResult() {
return result;
}
public int getOpened() {
return opened;
}
/** 每帧分片执行,最多展开 budget 个节点,不卡客户端线程。 */
public void step(int budget) {
if (state != State.RUNNING) {
return;
}
for (int b = 0; b < budget; b++) {
Node cur = poll();
if (cur == null) {
fail("autorail.err.walkFail");
return;
}
if (cur.pos.equals(goal)) {
rebuild(cur);
state = State.FOUND;
return;
}
expand(cur);
if (++expansions > MAX_EXPANSIONS) {
fail("autorail.err.walkFail");
return;
}
}
}
private Node poll() {
while (true) {
Node n = open.poll();
if (n == null) {
return null;
}
if (best.get(n.pos.asLong()) == n) {
return n;
}
}
}
private void expand(Node cur) {
for (Direction d : Direction.Type.HORIZONTAL) {
BlockPos base = cur.pos.offset(d);
// 跳上 1 格
if (passHead(cur.pos.up(2))) {
tryMove(cur, base.up(), COST_JUMP, true);
}
// 平走 / 涉水
tryMove(cur, base, 1.0f, false);
// 落下 1-3 格
for (int f = 1; f <= 3; f++) {
tryMove(cur, base.down(f), 1.0f + 0.25f * f, false);
}
}
}
private void tryMove(Node cur, BlockPos target, float baseCost, boolean jump) {
if (!inRegion(target) || !walkable(target) || !passHead(target.up())) {
return;
}
BlockState sup = world.getBlockState(target.down());
boolean solid = sup.isSolidBlock(world, target.down());
boolean water = sup.getFluidState().isIn(FluidTags.WATER);
if (!solid && !water) {
return;
}
if (jump && !solid) {
return; // 跳跃必须落在实心方块上
}
float cost = baseCost;
if (world.getBlockState(target).getFluidState().isIn(FluidTags.WATER)) {
cost += COST_WATER;
}
float g = cur.g + cost;
long key = target.asLong();
Node old = best.get(key);
if (old == null || g < old.g - 1.0e-4f) {
Node n = new Node(target, cur, g, g + h(target));
best.put(key, n);
open.add(n);
opened++;
}
}
private void rebuild(Node goalNode) {
result.clear();
for (Node n = goalNode; n != null; n = n.parent) {
result.add(n.pos);
}
java.util.Collections.reverse(result);
}
private void computeRegion() {
minX = Math.min(start.getX(), goal.getX()) - MARGIN;
maxX = Math.max(start.getX(), goal.getX()) + MARGIN;
minZ = Math.min(start.getZ(), goal.getZ()) - MARGIN;
maxZ = Math.max(start.getZ(), goal.getZ()) + MARGIN;
minY = Math.max(world.getBottomY() + 1, Math.min(start.getY(), goal.getY()) - MARGIN);
maxY = Math.min(world.getTopYInclusive() - 2, Math.max(start.getY(), goal.getY()) + MARGIN);
}
private boolean inRegion(BlockPos p) {
return p.getX() >= minX && p.getX() <= maxX
&& p.getY() >= minY && p.getY() <= maxY
&& p.getZ() >= minZ && p.getZ() <= maxZ;
}
private float h(BlockPos p) {
return 1.0f * (Math.abs(p.getX() - goal.getX())
+ Math.abs(p.getY() - goal.getY())
+ Math.abs(p.getZ() - goal.getZ()));
}
private void fail(String key) {
failKey = key;
state = State.FAILED;
}
/** 站立格:空气 / 可替换 / 铁轨(无碰撞)/ 水。岩浆不可走。 */
private boolean walkable(BlockPos pos) {
BlockState s = world.getBlockState(pos);
return s.isAir()
|| (s.getFluidState().isEmpty() && s.isReplaceable())
|| s.isOf(Blocks.RAIL)
|| s.isOf(Blocks.POWERED_RAIL)
|| s.isOf(Blocks.DETECTOR_RAIL)
|| s.getFluidState().isIn(FluidTags.WATER);
}
private boolean passHead(BlockPos pos) {
return walkable(pos);
}
private static final class Node {
final BlockPos pos;
final Node parent;
final float g;
final float f;
Node(BlockPos pos, Node parent, float g, float f) {
this.pos = pos;
this.parent = parent;
this.g = g;
this.f = f;
}
}
}