package net.minecraft.world.level.pathfinder; import java.util.HashSet; import java.util.List; import java.util.Set; import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.util.VisibleForDebug; import net.minecraft.world.entity.Entity; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public class Path { private final List nodes; @Nullable private Path.DebugData debugData; private int nextNodeIndex; private final BlockPos target; private final float distToTarget; private final boolean reached; public Path(List nodes, BlockPos target, boolean reached) { this.nodes = nodes; this.target = target; this.distToTarget = nodes.isEmpty() ? Float.MAX_VALUE : ((Node)this.nodes.get(this.nodes.size() - 1)).distanceManhattan(this.target); this.reached = reached; } /** * Directs this path to the next point in its array */ public void advance() { this.nextNodeIndex++; } public boolean notStarted() { return this.nextNodeIndex <= 0; } /** * Returns {@code true} if this path has reached the end */ public boolean isDone() { return this.nextNodeIndex >= this.nodes.size(); } /** * Returns the last {@link net.minecraft.world.level.pathfinder.Node} of the Array. */ @Nullable public Node getEndNode() { return !this.nodes.isEmpty() ? (Node)this.nodes.get(this.nodes.size() - 1) : null; } /** * Returns the {@link net.minecraft.world.level.pathfinder.Node} located at the specified index, usually the current one. */ public Node getNode(int index) { return (Node)this.nodes.get(index); } public void truncateNodes(int length) { if (this.nodes.size() > length) { this.nodes.subList(length, this.nodes.size()).clear(); } } public void replaceNode(int index, Node point) { this.nodes.set(index, point); } public int getNodeCount() { return this.nodes.size(); } public int getNextNodeIndex() { return this.nextNodeIndex; } public void setNextNodeIndex(int currentPathIndex) { this.nextNodeIndex = currentPathIndex; } /** * Gets the vector of the {@link net.minecraft.world.level.pathfinder.Node} associated with the given index. */ public Vec3 getEntityPosAtNode(Entity entity, int index) { Node node = (Node)this.nodes.get(index); double d = node.x + (int)(entity.getBbWidth() + 1.0F) * 0.5; double e = node.y; double f = node.z + (int)(entity.getBbWidth() + 1.0F) * 0.5; return new Vec3(d, e, f); } public BlockPos getNodePos(int index) { return ((Node)this.nodes.get(index)).asBlockPos(); } /** * @return the current {@code PathEntity} target node as a {@code Vec3D} */ public Vec3 getNextEntityPos(Entity entity) { return this.getEntityPosAtNode(entity, this.nextNodeIndex); } public BlockPos getNextNodePos() { return ((Node)this.nodes.get(this.nextNodeIndex)).asBlockPos(); } public Node getNextNode() { return (Node)this.nodes.get(this.nextNodeIndex); } @Nullable public Node getPreviousNode() { return this.nextNodeIndex > 0 ? (Node)this.nodes.get(this.nextNodeIndex - 1) : null; } /** * Returns {@code true} if the EntityPath are the same. Non instance related equals. */ public boolean sameAs(@Nullable Path pathentity) { if (pathentity == null) { return false; } else if (pathentity.nodes.size() != this.nodes.size()) { return false; } else { for (int i = 0; i < this.nodes.size(); i++) { Node node = (Node)this.nodes.get(i); Node node2 = (Node)pathentity.nodes.get(i); if (node.x != node2.x || node.y != node2.y || node.z != node2.z) { return false; } } return true; } } public boolean canReach() { return this.reached; } @VisibleForDebug void setDebug(Node[] openSet, Node[] closedSet, Set targetNodes) { this.debugData = new Path.DebugData(openSet, closedSet, targetNodes); } @Nullable public Path.DebugData debugData() { return this.debugData; } public void writeToStream(FriendlyByteBuf buffer) { if (this.debugData != null && !this.debugData.targetNodes.isEmpty()) { buffer.writeBoolean(this.reached); buffer.writeInt(this.nextNodeIndex); buffer.writeBlockPos(this.target); buffer.writeCollection(this.nodes, (friendlyByteBuf, node) -> node.writeToStream(friendlyByteBuf)); this.debugData.write(buffer); } } public static Path createFromStream(FriendlyByteBuf buf) { boolean bl = buf.readBoolean(); int i = buf.readInt(); BlockPos blockPos = buf.readBlockPos(); List list = buf.readList(Node::createFromStream); Path.DebugData debugData = Path.DebugData.read(buf); Path path = new Path(list, blockPos, bl); path.debugData = debugData; path.nextNodeIndex = i; return path; } public String toString() { return "Path(length=" + this.nodes.size() + ")"; } public BlockPos getTarget() { return this.target; } public float getDistToTarget() { return this.distToTarget; } static Node[] readNodeArray(FriendlyByteBuf buffer) { Node[] nodes = new Node[buffer.readVarInt()]; for (int i = 0; i < nodes.length; i++) { nodes[i] = Node.createFromStream(buffer); } return nodes; } static void writeNodeArray(FriendlyByteBuf buffer, Node[] nodeArray) { buffer.writeVarInt(nodeArray.length); for (Node node : nodeArray) { node.writeToStream(buffer); } } public Path copy() { Path path = new Path(this.nodes, this.target, this.reached); path.debugData = this.debugData; path.nextNodeIndex = this.nextNodeIndex; return path; } public record DebugData(Node[] openSet, Node[] closedSet, Set targetNodes) { public void write(FriendlyByteBuf buffer) { buffer.writeCollection(this.targetNodes, (friendlyByteBuf, target) -> target.writeToStream(friendlyByteBuf)); Path.writeNodeArray(buffer, this.openSet); Path.writeNodeArray(buffer, this.closedSet); } public static Path.DebugData read(FriendlyByteBuf buffer) { HashSet hashSet = buffer.readCollection(HashSet::new, Target::createFromStream); Node[] nodes = Path.readNodeArray(buffer); Node[] nodes2 = Path.readNodeArray(buffer); return new Path.DebugData(nodes, nodes2, hashSet); } } }