feat: 电气化间隔放置+自动走近起点+材料预案+避险进食
这个提交包含在:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户