more mutability

This commit is contained in:
iam4722202468 2024-03-04 18:28:36 -05:00
parent 21c8dec051
commit c063688be0
No known key found for this signature in database
GPG Key ID: 4D8728E97DA5B8F9
10 changed files with 223 additions and 121 deletions

View File

@ -268,6 +268,10 @@ public final class BoundingBox implements Shape {
reset(boundingBox, p.x(), p.y(), p.z(), axisMask, (int) axis);
}
public void reset(BoundingBox boundingBox, double x, double y, double z, AxisMask axisMask, double axis) {
reset(boundingBox, x, y, z, axisMask, (int) axis);
}
@Override
public boolean hasNext() {
return x <= maxX && y <= maxY && z <= maxZ;

View File

@ -28,7 +28,7 @@ public final class Navigator {
private Point goalPosition;
private final Entity entity;
// Essentially a double buffer. Wait until a path is done computing before replpacing the old one.
// Essentially a double buffer. Wait until a path is done computing before replacing the old one.
private PPath computingPath;
private PPath path;
@ -66,7 +66,7 @@ public final class Navigator {
* @param maxDistance maximum search distance
* @param pathVariance how far to search off of the direct path. For open worlds, this can be low (around 20) and for large mazes this needs to be very high.
* @param onComplete called when the path has been completed
* @return true if a path has been found
* @return true if a path is being generated
*/
public synchronized boolean setPathTo(@Nullable Point point, double minimumDistance, double maxDistance, double pathVariance, @Nullable Runnable onComplete) {
final Instance instance = entity.getInstance();
@ -115,9 +115,8 @@ public final class Navigator {
this.nodeGenerator,
onComplete);
final boolean success = computingPath != null;
this.goalPosition = success ? point : null;
return success;
this.goalPosition = point;
return true;
}
@ApiStatus.Internal
@ -136,7 +135,7 @@ public final class Navigator {
path.setState(PPath.PathState.FOLLOWING);
// Remove nodes that are too close to the start. Prevents doubling back to hit points that have already been hit
for (int i = 0; i < path.getNodes().size(); i++) {
if (path.getNodes().get(i).point().sameBlock(entity.getPosition())) {
if (isSameBlock(path.getNodes().get(i), entity.getPosition())) {
path.getNodes().subList(0, i).clear();
break;
}
@ -241,9 +240,12 @@ public final class Navigator {
if (path == null) return;
for (PNode point : path.getNodes()) {
Point pos = point.point();
var packet = new ParticlePacket(Particle.COMPOSTER, pos.x(), pos.y() + 0.5, pos.z(), 0, 0, 0, 0, 1);
var packet = new ParticlePacket(Particle.COMPOSTER, point.x(), point.y() + 0.5, point.z(), 0, 0, 0, 0, 1);
entity.sendPacketToViewers(packet);
}
}
private static boolean isSameBlock(PNode pNode, Pos position) {
return Math.floor(pNode.x()) == position.blockX() && Math.floor(pNode.y()) == position.blockY() && Math.floor(pNode.z()) == position.blockZ();
}
}

View File

@ -20,7 +20,9 @@ public class PNode {
private double g;
private double h;
private PNode parent;
private Point point;
private double pointX;
private double pointY;
private double pointZ;
private int hashCode;
private int cantor(int a, int b) {
@ -31,17 +33,23 @@ public class PNode {
private NodeType type;
public PNode(@NotNull Point point, double g, double h, @Nullable PNode parent) {
this(point, g, h, NodeType.WALK, parent);
public PNode(double px, double py, double pz, double g, double h, @Nullable PNode parent) {
this(px, py, pz, g, h, NodeType.WALK, parent);
}
public PNode(@NotNull Point point, double g, double h, @NotNull NodeType type, @Nullable PNode parent) {
this.point = new Vec(point.x(), point.y(), point.z());
public PNode(double px, double py, double pz, double g, double h, @NotNull NodeType type, @Nullable PNode parent) {
this.g = g;
this.h = h;
this.parent = parent;
this.hashCode = cantor(point.blockX(), cantor(point.blockY(), point.blockZ()));
this.type = type;
this.pointX = px;
this.pointY = py;
this.pointZ = pz;
this.hashCode = cantor((int) Math.floor(px), cantor((int) Math.floor(py), (int) Math.floor(pz)));
}
public PNode(Point point, double g, double h, NodeType walk, @Nullable PNode parent) {
this(point.x(), point.y(), point.z(), g, h, walk, parent);
}
@Override
@ -60,15 +68,37 @@ public class PNode {
@Override
public String toString() {
return "PNode{" +
"point=" + point +
"point=" + pointX + ", " + pointY + ", " + pointZ +
", d=" + (g + h) +
", type=" + type +
'}';
}
@ApiStatus.Internal
public Point point() {
return point;
public double x() {
return pointX;
}
@ApiStatus.Internal
public double y() {
return pointY;
}
@ApiStatus.Internal
public double z() {
return pointZ;
}
public int blockX() {
return (int) Math.floor(pointX);
}
public int blockY() {
return (int) Math.floor(pointY);
}
public int blockZ() {
return (int) Math.floor(pointZ);
}
@ApiStatus.Internal
@ -102,9 +132,11 @@ public class PNode {
}
@ApiStatus.Internal
public void setPoint(@NotNull Point point) {
this.point = point;
this.hashCode = cantor(point.blockX(), cantor(point.blockY(), point.blockZ()));
public void setPoint(double px, double py, double pz) {
this.pointX = px;
this.pointY = py;
this.pointZ = pz;
this.hashCode = cantor((int) Math.floor(px), cantor((int) Math.floor(py), (int) Math.floor(pz)));
}
@ApiStatus.Internal

View File

@ -1,6 +1,7 @@
package net.minestom.server.entity.pathfinding;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -20,7 +21,7 @@ public class PPath {
public Point getNext() {
if (index + 1 >= nodes.size()) return null;
var current = nodes.get(index + 1);
return current.point();
return new Vec(current.x(), current.y(), current.z());
}
public void setState(@NotNull PathState newState) {
@ -67,7 +68,7 @@ public class PPath {
Point getCurrent() {
if (index >= nodes.size()) return null;
var current = nodes.get(index);
return current.point();
return new Vec(current.x(), current.y(), current.z());
}
void next() {

View File

@ -5,7 +5,6 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashBigSet;
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.pathfinding.generators.NodeGenerator;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;
@ -17,20 +16,18 @@ import java.util.concurrent.Executors;
public class PathGenerator {
private static final ExecutorService pool = Executors.newWorkStealingPool();
private static final PNode repathNode = new PNode(Pos.ZERO, 0, 0, PNode.NodeType.REPATH, null);
private static Comparator<PNode> pNodeComparator = (s1, s2) -> (int) (((s1.g() + s1.h()) - (s2.g() + s2.h())) * 1000);
private static final PNode repathNode = new PNode(0, 0, 0, 0, 0, PNode.NodeType.REPATH, null);
private static final Comparator<PNode> pNodeComparator = (s1, s2) -> (int) (((s1.g() + s1.h()) - (s2.g() + s2.h())) * 1000);
public static @Nullable PPath generate(@NotNull Instance instance, @NotNull Pos orgStart, @NotNull Point orgTarget, double closeDistance, double maxDistance, double pathVariance, @NotNull BoundingBox boundingBox, boolean isOnGround, @NotNull NodeGenerator generator, @Nullable Runnable onComplete) {
public static @NotNull PPath generate(@NotNull Instance instance, @NotNull Pos orgStart, @NotNull Point orgTarget, double closeDistance, double maxDistance, double pathVariance, @NotNull BoundingBox boundingBox, boolean isOnGround, @NotNull NodeGenerator generator, @Nullable Runnable onComplete) {
Point start = (!isOnGround && generator.hasGravitySnap())
? generator.gravitySnap(instance, orgStart, boundingBox, 100)
? orgStart.withY(generator.gravitySnap(instance, orgStart.x(), orgStart.y(), orgStart.z(), boundingBox, 100))
: orgStart;
Point target = (generator.hasGravitySnap())
? generator.gravitySnap(instance, orgTarget, boundingBox, 100)
? orgTarget.withY(generator.gravitySnap(instance, orgTarget.x(), orgTarget.y(), orgTarget.z(), boundingBox, 100))
: Pos.fromPoint(orgTarget);
if (start == null || target == null) return null;
PPath path = new PPath(maxDistance, pathVariance, onComplete);
pool.submit(() -> computePath(instance, start, target, closeDistance, maxDistance, pathVariance, boundingBox, path, generator));
@ -60,13 +57,13 @@ public class PathGenerator {
PNode current = open.dequeue();
var chunk = instance.getChunkAt(current.point());
var chunk = instance.getChunkAt(current.x(), current.z());
if (chunk == null) continue;
if (!chunk.isLoaded()) continue;
if (((current.g() + current.h()) - straightDistance) > pathVariance) continue;
if (!withinDistance(current.point(), start, maxDistance)) continue;
if (withinDistance(current.point(), target, closeDistance)) {
if (!withinDistance(current, start, maxDistance)) continue;
if (withinDistance(current, target, closeDistance)) {
open.enqueue(current);
break;
}
@ -78,7 +75,7 @@ public class PathGenerator {
Collection<? extends PNode> found = generator.getWalkable(instance, closed, current, target, boundingBox);
found.forEach(p -> {
if (p.point().distance(start) <= maxDistance) {
if (getDistanceSquared(p.x(), p.y(), p.z(), start) <= (maxDistance * maxDistance)) {
open.enqueue(p);
closed.add(p);
}
@ -87,7 +84,7 @@ public class PathGenerator {
PNode current = open.isEmpty() ? null : open.dequeue();
if (current == null || open.isEmpty() || !withinDistance(current.point(), target, closeDistance)) {
if (current == null || open.isEmpty() || !withinDistance(current, target, closeDistance)) {
if (closestFoundNodes.isEmpty()) {
path.setState(PPath.PathState.INVALID);
return;
@ -115,7 +112,7 @@ public class PathGenerator {
}
var lastNode = path.getNodes().get(path.getNodes().size() - 1);
if (lastNode.point().distance(target) > closeDistance) {
if (getDistanceSquared(lastNode.x(), lastNode.y(), lastNode.z(), target) > (closeDistance * closeDistance)) {
path.setState(PPath.PathState.BEST_EFFORT);
return;
}
@ -125,7 +122,14 @@ public class PathGenerator {
path.setState(PPath.PathState.COMPUTED);
}
private static boolean withinDistance(Point point, Point target, double closeDistance) {
return point.distanceSquared(target) < (closeDistance * closeDistance);
private static boolean withinDistance(PNode point, Point target, double closeDistance) {
return getDistanceSquared(point.x(), point.y(), point.z(), target) < (closeDistance * closeDistance);
}
private static double getDistanceSquared(double x, double y, double z, Point target) {
double dx = x - target.x();
double dy = y - target.y();
double dz = z - target.z();
return dx * dx + dy * dy + dz * dz;
}
}

View File

@ -2,11 +2,10 @@ package net.minestom.server.entity.pathfinding.generators;
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.pathfinding.PNode;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
@ -18,7 +17,7 @@ public class FlyingNodeGenerator implements NodeGenerator {
@Override
public @NotNull Collection<? extends PNode> getWalkable(@NotNull Instance instance, @NotNull Set<PNode> visited, @NotNull PNode current, @NotNull Point goal, @NotNull BoundingBox boundingBox) {
Collection<PNode> nearby = new ArrayList<>();
tempNode = new PNode(Pos.ZERO, 0, 0, current);
tempNode = new PNode(0, 0, 0, 0, 0, current);
int stepSize = (int) Math.max(Math.floor(boundingBox.width() / 2), 1);
if (stepSize < 1) stepSize = 1;
@ -28,29 +27,43 @@ public class FlyingNodeGenerator implements NodeGenerator {
if (x == 0 && z == 0) continue;
double cost = Math.sqrt(x * x + z * z) * 0.98;
Point currentLevelPoint = current.point().withX(current.point().blockX() + 0.5 + x).withZ(current.point().blockZ() + 0.5 + z).withY(current.point().blockY() + 0.5);
Point upPoint = current.point().withX(current.point().blockX() + 0.5 + x).withZ(current.point().blockZ() + 0.5 + z).withY(current.point().blockY() + 1 + 0.5);
Point downPoint = current.point().withX(current.point().blockX() + 0.5 + x).withZ(current.point().blockZ() + 0.5 + z).withY(current.point().blockY() - 1 + 0.5);
double currentLevelPointX = current.blockX() + 0.5 + x;
double currentLevelPointY = current.blockY() + 0.5;
double currentLevelPointZ = current.blockZ() + 0.5 + z;
var nodeWalk = createFly(instance, currentLevelPoint, boundingBox, cost, current, goal, visited);
double upPointX = current.blockX() + 0.5 + x;
double upPointY = current.blockY() + 1 + 0.5;
double upPointZ = current.blockZ() + 0.5 + z;
double downPointX = current.blockX() + 0.5 + x;
double downPointY = current.blockY() - 1 + 0.5;
double downPointZ = current.blockZ() + 0.5 + z;
var nodeWalk = createFly(instance, new Vec(currentLevelPointX, currentLevelPointY, currentLevelPointZ), boundingBox, cost, current, goal, visited);
if (nodeWalk != null && !visited.contains(nodeWalk)) nearby.add(nodeWalk);
var nodeJump = createFly(instance, upPoint, boundingBox, cost, current, goal, visited);
var nodeJump = createFly(instance, new Vec(upPointX, upPointY, upPointZ), boundingBox, cost, current, goal, visited);
if (nodeJump != null && !visited.contains(nodeJump)) nearby.add(nodeJump);
var nodeFall = createFly(instance, downPoint, boundingBox, cost, current, goal, visited);
var nodeFall = createFly(instance, new Vec(downPointX, downPointY, downPointZ), boundingBox, cost, current, goal, visited);
if (nodeFall != null && !visited.contains(nodeFall)) nearby.add(nodeFall);
}
}
// Straight up
Point upPoint = current.point().withY(current.point().blockY() + 1 + 0.5);
var nodeJump = createFly(instance, upPoint, boundingBox, 2, current, goal, visited);
double upPointX = current.x();
double upPointY = current.blockY() + 1 + 0.5;
double upPointZ = current.z();
var nodeJump = createFly(instance, new Vec(upPointX, upPointY, upPointZ), boundingBox, 2, current, goal, visited);
if (nodeJump != null && !visited.contains(nodeJump)) nearby.add(nodeJump);
// Straight down
Point downPoint = current.point().withY(current.point().blockY() - 1 + 0.5);
var nodeFall = createFly(instance, downPoint, boundingBox, 2, current, goal, visited);
double downPointX = current.x();
double downPointY = current.blockY() - 1 + 0.5;
double downPointZ = current.z();
var nodeFall = createFly(instance, new Vec(downPointX, downPointY, downPointZ), boundingBox, 2, current, goal, visited);
if (nodeFall != null && !visited.contains(nodeFall)) nearby.add(nodeFall);
return nearby;
@ -64,7 +77,7 @@ public class FlyingNodeGenerator implements NodeGenerator {
private PNode createFly(Instance instance, Point point, BoundingBox boundingBox, double cost, PNode start, Point goal, Set<PNode> closed) {
var n = newNode(start, cost, point, goal);
if (closed.contains(n)) return null;
if (!canMoveTowards(instance, start.point(), point, boundingBox)) return null;
if (!canMoveTowards(instance, new Vec(start.x(), start.y(), start.z()), point, boundingBox)) return null;
n.setType(PNode.NodeType.FLY);
return n;
}
@ -72,16 +85,16 @@ public class FlyingNodeGenerator implements NodeGenerator {
private PNode newNode(PNode current, double cost, Point point, Point goal) {
tempNode.setG(current.g() + cost);
tempNode.setH(heuristic(point, goal));
tempNode.setPoint(point);
tempNode.setPoint(point.x(), point.y(), point.z());
var newNode = tempNode;
tempNode = new PNode(Pos.ZERO, 0, 0, PNode.NodeType.WALK, current);
tempNode = new PNode(0, 0, 0, 0, 0, PNode.NodeType.WALK, current);
return newNode;
}
@Override
public @Nullable Point gravitySnap(@NotNull Instance instance, @NotNull Point point, @NotNull BoundingBox boundingBox, double maxFall) {
return null;
public double gravitySnap(@NotNull Instance instance, double pointX, double pointY, double pointZ, @NotNull BoundingBox boundingBox, double maxFall) {
return pointY;
}
}

View File

@ -17,12 +17,12 @@ import java.util.Set;
public class GroundNodeGenerator implements NodeGenerator {
private PNode tempNode = null;
private BoundingBox.PointIterator pointIterator = new BoundingBox.PointIterator();
private final BoundingBox.PointIterator pointIterator = new BoundingBox.PointIterator();
@Override
public @NotNull Collection<? extends PNode> getWalkable(@NotNull Instance instance, @NotNull Set<PNode> visited, @NotNull PNode current, @NotNull Point goal, @NotNull BoundingBox boundingBox) {
Collection<PNode> nearby = new ArrayList<>();
tempNode = new PNode(Pos.ZERO, 0, 0, current);
tempNode = new PNode(0, 0, 0, 0, 0, current);
int stepSize = (int) Math.max(Math.floor(boundingBox.width() / 2), 1);
if (stepSize < 1) stepSize = 1;
@ -32,18 +32,22 @@ public class GroundNodeGenerator implements NodeGenerator {
if (x == 0 && z == 0) continue;
double cost = Math.sqrt(x * x + z * z) * 0.98;
Point floorPoint = new Vec(current.point().blockX() + 0.5 + x, current.point().blockY(), current.point().blockZ() + 0.5 + z);
floorPoint = gravitySnap(instance, floorPoint, boundingBox, 5);
if (floorPoint == null) continue;
double floorPointX = current.blockX() + 0.5 + x;
double floorPointY = current.blockY();
double floorPointZ = current.blockZ() + 0.5 + z;
floorPointY = gravitySnap(instance, floorPointX, floorPointY, floorPointZ, boundingBox, 5);
var floorPoint = new Vec(floorPointX, floorPointY, floorPointZ);
var nodeWalk = createWalk(instance, floorPoint, boundingBox, cost, current, goal, visited);
if (nodeWalk != null && !visited.contains(nodeWalk)) nearby.add(nodeWalk);
for (int i = 1; i <= 1; ++i) {
Point jumpPoint = new Vec(current.point().blockX() + 0.5 + x, current.point().blockY() + i, current.point().blockZ() + 0.5 + z);
jumpPoint = gravitySnap(instance, jumpPoint, boundingBox, 5);
Point jumpPoint = new Vec(current.blockX() + 0.5 + x, current.blockY() + i, current.blockZ() + 0.5 + z);
var jumpPointY = gravitySnap(instance, jumpPoint.x(), jumpPoint.y(), jumpPoint.z(), boundingBox, 5);
jumpPoint = jumpPoint.withY(jumpPointY);
if (jumpPoint == null) continue;
if (!floorPoint.sameBlock(jumpPoint)) {
var nodeJump = createJump(instance, jumpPoint, boundingBox, cost + 0.2, current, goal, visited);
if (nodeJump != null && !visited.contains(nodeJump)) nearby.add(nodeJump);
@ -64,25 +68,25 @@ public class GroundNodeGenerator implements NodeGenerator {
var n = newNode(start, cost, point, goal);
if (closed.contains(n)) return null;
if (Math.abs(point.y() - start.point().y()) > Vec.EPSILON && point.y() < start.point().y()) {
if (!canMoveTowards(instance, start.point(), point.withY(start.point().y()), boundingBox)) return null;
if (Math.abs(point.y() - start.y()) > Vec.EPSILON && point.y() < start.y()) {
if (!canMoveTowards(instance, new Vec(start.x(), start.y(), start.z()), point.withY(start.y()), boundingBox)) return null;
n.setType(PNode.NodeType.FALL);
} else {
if (!canMoveTowards(instance, start.point(), point, boundingBox)) return null;
if (!canMoveTowards(instance, new Vec(start.x(), start.y(), start.z()), point, boundingBox)) return null;
}
return n;
}
private PNode createJump(Instance instance, Point point, BoundingBox boundingBox, double cost, PNode start, Point goal, Set<PNode> closed) {
if (Math.abs(point.y() - start.point().y()) < Vec.EPSILON) return null;
if (point.y() - start.point().y() > 2) return null;
if (point.blockX() != start.point().blockX() && point.blockZ() != start.point().blockZ()) return null;
if (Math.abs(point.y() - start.y()) < Vec.EPSILON) return null;
if (point.y() - start.y() > 2) return null;
if (point.blockX() != start.blockX() && point.blockZ() != start.blockZ()) return null;
var n = newNode(start, cost, point, goal);
if (closed.contains(n)) return null;
if (pointInvalid(instance, point, boundingBox)) return null;
if (pointInvalid(instance, start.point().add(0, 1, 0), boundingBox)) return null;
if (pointInvalid(instance, new Vec(start.x(), start.y() + 1, start.z()), boundingBox)) return null;
n.setType(PNode.NodeType.JUMP);
return n;
@ -91,22 +95,22 @@ public class GroundNodeGenerator implements NodeGenerator {
private PNode newNode(PNode current, double cost, Point point, Point goal) {
tempNode.setG(current.g() + cost);
tempNode.setH(heuristic(point, goal));
tempNode.setPoint(point);
tempNode.setPoint(point.x(), point.y(), point.z());
var newNode = tempNode;
tempNode = new PNode(Pos.ZERO, 0, 0, PNode.NodeType.WALK, current);
tempNode = new PNode(0, 0, 0, 0, 0, PNode.NodeType.WALK, current);
return newNode;
}
@Override
public @Nullable Point gravitySnap(@NotNull Instance instance, @NotNull Point pointOrg, @NotNull BoundingBox boundingBox, double maxFall) {
double pointX = pointOrg.blockX() + 0.5;
double pointY = pointOrg.blockY();
double pointZ = pointOrg.blockZ() + 0.5;
public double gravitySnap(@NotNull Instance instance, double pointOrgX, double pointOrgY, double pointOrgZ, @NotNull BoundingBox boundingBox, double maxFall) {
double pointX = (int) Math.floor(pointOrgX) + 0.5;
double pointY = (int) Math.floor(pointOrgY);
double pointZ = (int) Math.floor(pointOrgZ) + 0.5;
Chunk c = instance.getChunkAt(pointX, pointZ);
if (c == null) return null;
if (c == null) return pointY;
for (int axis = 1; axis <= maxFall; ++axis) {
pointIterator.reset(boundingBox, pointX, pointY, pointZ, BoundingBox.AxisMask.Y, -axis);
@ -115,11 +119,11 @@ public class GroundNodeGenerator implements NodeGenerator {
var block = pointIterator.next();
if (instance.getBlock(block.blockX(), block.blockY(), block.blockZ(), Block.Getter.Condition.TYPE).isSolid()) {
return new Vec(pointX, block.blockY() + 1, pointZ);
return block.blockY() + 1;
}
}
}
return new Vec(pointX, pointY - maxFall, pointZ);
return pointY - maxFall;
}
}

View File

@ -10,7 +10,6 @@ import net.minestom.server.entity.pathfinding.PNode;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Set;
@ -34,13 +33,16 @@ public interface NodeGenerator {
boolean hasGravitySnap();
/**
* Snap the point to the ground.
* @param point the point
* Snap point to the ground
* @param instance the instance
* @param pointX the x coordinate
* @param pointY the y coordinate
* @param pointZ the z coordinate
* @param boundingBox the bounding box
* @param maxFall the maximum distance to snap down
* @return the snapped point
* @param maxFall the maximum fall distance
* @return the snapped y coordinate
*/
@Nullable Point gravitySnap(@NotNull Instance instance, @NotNull Point point, @NotNull BoundingBox boundingBox, double maxFall);
double gravitySnap(@NotNull Instance instance, double pointX, double pointY, double pointZ, @NotNull BoundingBox boundingBox, double maxFall);
/**
* Check if we can move directly from one point to another

View File

@ -2,13 +2,12 @@ package net.minestom.server.entity.pathfinding.generators;
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.pathfinding.PNode;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
@ -16,12 +15,12 @@ import java.util.Set;
public class WaterNodeGenerator implements NodeGenerator {
private PNode tempNode = null;
private BoundingBox.PointIterator pointIterator = new BoundingBox.PointIterator();
private final BoundingBox.PointIterator pointIterator = new BoundingBox.PointIterator();
@Override
public @NotNull Collection<? extends PNode> getWalkable(@NotNull Instance instance, @NotNull Set<PNode> visited, @NotNull PNode current, @NotNull Point goal, @NotNull BoundingBox boundingBox) {
Collection<PNode> nearby = new ArrayList<>();
tempNode = new PNode(Pos.ZERO, 0, 0, current);
tempNode = new PNode(0, 0, 0, 0, 0, current);
int stepSize = (int) Math.max(Math.floor(boundingBox.width() / 2), 1);
if (stepSize < 1) stepSize = 1;
@ -31,38 +30,52 @@ public class WaterNodeGenerator implements NodeGenerator {
if (x == 0 && z == 0) continue;
double cost = Math.sqrt(x * x + z * z) * 0.98;
Point currentLevelPoint = current.point().withX(current.point().blockX() + 0.5 + x).withZ(current.point().blockZ() + 0.5 + z).withY(current.point().blockY() + 0.5);
Point upPoint = current.point().withX(current.point().blockX() + 0.5 + x).withZ(current.point().blockZ() + 0.5 + z).withY(current.point().blockY() + 1 + 0.5);
Point downPoint = current.point().withX(current.point().blockX() + 0.5 + x).withZ(current.point().blockZ() + 0.5 + z).withY(current.point().blockY() - 1 + 0.5);
double currentLevelPointX = current.blockX() + 0.5 + x;
double currentLevelPointY = current.blockY();
double currentLevelPointZ = current.blockZ() + 0.5 + z;
if (instance.getBlock(currentLevelPoint).compare(Block.WATER)) {
var nodeWalk = createFly(instance, currentLevelPoint, boundingBox, cost, current, goal, visited);
double upPointX = current.blockX() + 0.5 + x;
double upPointY = current.blockY() + 1 + 0.5;
double upPointZ = current.blockZ() + 0.5 + z;
double downPointX = current.blockX() + 0.5 + x;
double downPointY = current.blockY() - 1 + 0.5;
double downPointZ = current.blockZ() + 0.5 + z;
if (instance.getBlock((int) Math.floor(currentLevelPointX), (int) Math.floor(currentLevelPointY), (int) Math.floor(currentLevelPointZ)).compare(Block.WATER)) {
var nodeWalk = createFly(instance, new Vec(currentLevelPointX, currentLevelPointY, currentLevelPointZ), boundingBox, cost, current, goal, visited);
if (nodeWalk != null && !visited.contains(nodeWalk)) nearby.add(nodeWalk);
}
if (instance.getBlock(upPoint).compare(Block.WATER)) {
var nodeJump = createFly(instance, upPoint, boundingBox, cost, current, goal, visited);
if (instance.getBlock((int) Math.floor(upPointX), (int) Math.floor(upPointY), (int) Math.floor(upPointZ)).compare(Block.WATER)) {
var nodeJump = createFly(instance, new Vec(upPointX, upPointY, upPointZ), boundingBox, cost, current, goal, visited);
if (nodeJump != null && !visited.contains(nodeJump)) nearby.add(nodeJump);
}
if (instance.getBlock(downPoint).compare(Block.WATER)) {
var nodeFall = createFly(instance, downPoint, boundingBox, cost, current, goal, visited);
if (instance.getBlock((int) Math.floor(downPointX), (int) Math.floor(downPointY), (int) Math.floor(downPointZ)).compare(Block.WATER)) {
var nodeFall = createFly(instance, new Vec(downPointX, downPointY, downPointZ), boundingBox, cost, current, goal, visited);
if (nodeFall != null && !visited.contains(nodeFall)) nearby.add(nodeFall);
}
}
}
// Straight up
Point upPoint = current.point().withY(current.point().blockY() + 1 + 0.5);
if (instance.getBlock(upPoint).compare(Block.WATER)) {
var nodeJump = createFly(instance, upPoint, boundingBox, 2, current, goal, visited);
double upPointX = current.x();
double upPointY = current.blockY() + 1 + 0.5;
double upPointZ = current.z();
if (instance.getBlock((int) Math.floor(upPointX), (int) Math.floor(upPointY), (int) Math.floor(upPointZ)).compare(Block.WATER)) {
var nodeJump = createFly(instance, new Vec(current.x(), current.y(), current.z()), boundingBox, 2, current, goal, visited);
if (nodeJump != null && !visited.contains(nodeJump)) nearby.add(nodeJump);
}
// Straight down
Point downPoint = current.point().withY(current.point().blockY() - 1 + 0.5);
if (instance.getBlock(downPoint).compare(Block.WATER)) {
var nodeFall = createFly(instance, downPoint, boundingBox, 2, current, goal, visited);
double downPointX = current.x();
double downPointY = current.blockY() - 1 + 0.5;
double downPointZ = current.z();
if (instance.getBlock((int) Math.floor(downPointX), (int) Math.floor(downPointY), (int) Math.floor(downPointZ)).compare(Block.WATER)) {
var nodeFall = createFly(instance, new Vec(downPointX, downPointY, downPointZ), boundingBox, 2, current, goal, visited);
if (nodeFall != null && !visited.contains(nodeFall)) nearby.add(nodeFall);
}
@ -77,7 +90,7 @@ public class WaterNodeGenerator implements NodeGenerator {
private PNode createFly(Instance instance, Point point, BoundingBox boundingBox, double cost, PNode start, Point goal, Set<PNode> closed) {
var n = newNode(start, cost, point, goal);
if (closed.contains(n)) return null;
if (!canMoveTowards(instance, start.point(), point, boundingBox)) return null;
if (!canMoveTowards(instance, new Vec(start.x(), start.y(), start.z()), point, boundingBox)) return null;
n.setType(PNode.NodeType.FLY);
return n;
}
@ -85,22 +98,24 @@ public class WaterNodeGenerator implements NodeGenerator {
private PNode newNode(PNode current, double cost, Point point, Point goal) {
tempNode.setG(current.g() + cost);
tempNode.setH(heuristic(point, goal));
tempNode.setPoint(point);
tempNode.setPoint(point.x(), point.y(), point.z());
var newNode = tempNode;
tempNode = new PNode(Pos.ZERO, 0, 0, PNode.NodeType.WALK, current);
tempNode = new PNode(0, 0, 0, 0, 0, PNode.NodeType.WALK, current);
return newNode;
}
public @Nullable Point gravitySnap(@NotNull Instance instance, @NotNull Point point, @NotNull BoundingBox boundingBox, double maxFall) {
point = new Pos(point.blockX() + 0.5, point.blockY(), point.blockZ() + 0.5);
public double gravitySnap(@NotNull Instance instance, double pointOrgX, double pointOrgY, double pointOrgZ, @NotNull BoundingBox boundingBox, double maxFall) {
double pointX = (int) Math.floor(pointOrgX) + 0.5;
double pointY = (int) Math.floor(pointOrgY);
double pointZ = (int) Math.floor(pointOrgZ) + 0.5;
Chunk c = instance.getChunkAt(point);
if (c == null) return null;
Chunk c = instance.getChunkAt(pointX, pointZ);
if (c == null) return pointY;
for (int axis = 1; axis <= maxFall; ++axis) {
pointIterator.reset(boundingBox, point, BoundingBox.AxisMask.Y, -axis);
pointIterator.reset(boundingBox, pointX, pointY, pointZ, BoundingBox.AxisMask.Y, -axis);
while (pointIterator.hasNext()) {
var block = pointIterator.next();
@ -108,11 +123,11 @@ public class WaterNodeGenerator implements NodeGenerator {
var foundBlock = instance.getBlock(block.blockX(), block.blockY(), block.blockZ(), Block.Getter.Condition.TYPE);
// Stop falling when water is hit
if (foundBlock.isSolid() || foundBlock.compare(Block.WATER)) {
return point.withY(block.blockY() + 1);
return block.blockY() + 1;
}
}
}
return point.withY(point.y() - maxFall);
return pointY - maxFall;
}
}

View File

@ -15,7 +15,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@EnvTest
public class PathfinderIntegrationTest {
@ -31,7 +32,7 @@ public class PathfinderIntegrationTest {
if (nodes.size() == 0) fail("Path is empty");
nodes.forEach((node) -> {
if (instance.getBlock(node.point()).isSolid()) {
if (instance.getBlock(node.blockX(), node.blockY(), node.blockZ()).isSolid()) {
fail("Node is inside a block");
}
});
@ -106,6 +107,32 @@ public class PathfinderIntegrationTest {
validateNodes(nav.getNodes(), i);
}
@Test
public void testBug(Env env) {
var i = env.createFlatInstance();
i.getWorldBorder().setCenter(0, 0);
i.getWorldBorder().setDiameter(10000);
ChunkUtils.forChunksInRange(0, 0, 10, (x, z) -> {
i.loadChunk(x, z).join();
});
var zombie = new LivingEntity(EntityType.ZOMBIE);
zombie.setInstance(i, new Pos(43.972731367054266, 40.000000000040735, -39.89155139999369));
zombie.tick(0);
zombie.tick(0);
Navigator nav = new Navigator(zombie);
nav.setPathTo(new Pos(43.5, 40, -41.5));
while (nav.getState() == PPath.PathState.CALCULATING) {}
assert(nav.getNodes() != null);
validateNodes(nav.getNodes(), i);
}
@Test
public void testPFNodeEqual(Env env) {
PNode node1 = new PNode(new Pos(0.777, 0, 0), 2, 0, PNode.NodeType.WALK, null);
@ -167,8 +194,6 @@ public class PathfinderIntegrationTest {
nav.setPathTo(new Pos(0, 40, 10));
while (nav.getState() == PPath.PathState.CALCULATING) {}
System.out.println(nav.getNodes());
assert(nav.getNodes() != null);
validateNodes(nav.getNodes(), i);
}
@ -187,7 +212,7 @@ public class PathfinderIntegrationTest {
var nodeGenerator = new GroundNodeGenerator();
var snapped = nodeGenerator.gravitySnap(i, new Pos(-140.74433362614695, 40.58268292446131, 18.87966960447388), zombie.getBoundingBox(), 100);
assertEquals(new Pos(-140.5, 40.0, 18.5), snapped);
var snapped = nodeGenerator.gravitySnap(i, -140.74433362614695, 40.58268292446131, 18.87966960447388, zombie.getBoundingBox(), 100);
assertEquals(40.0, snapped);
}
}