Use Krystilize pathfinder

This commit is contained in:
themode 2022-03-12 00:11:40 +01:00
parent d553dec4e1
commit a43fc980c1
15 changed files with 213 additions and 638 deletions

View File

@ -63,8 +63,6 @@ dependencies {
// Libraries
api(libs.gson)
implementation(libs.jcTools)
// Path finding
api(libs.hydrazine)
// Adventure, for user-interface
api(libs.bundles.adventure)

View File

@ -5,7 +5,6 @@ metadata.format.version = "1.1"
# Important dependencies
adventure = "4.9.3"
kotlin = "1.6.10"
hydrazine = "1.7.2"
dependencyGetter = "v1.0.1"
minestomData = "895581d464"
hephaistos = "2.4.2"
@ -51,7 +50,6 @@ kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", vers
kotlin-stdlib-jdk8 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" }
# Miscellaneous
hydrazine = { group = "com.github.MadMartian", name = "hydrazine-path-finding", version.ref = "hydrazine" }
dependencyGetter = { group = "com.github.Minestom", name = "DependencyGetter", version.ref = "dependencyGetter" }
minestomData = { group = "com.github.Minestom", name = "MinestomDataGenerator", version.ref = "minestomData" }
jetbrainsAnnotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrainsAnnotations" }

View File

@ -1,14 +1,11 @@
package net.minestom.server.entity;
import com.extollit.gaming.ai.path.HydrazinePathFinder;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.ai.EntityAI;
import net.minestom.server.entity.ai.EntityAIGroup;
import net.minestom.server.entity.pathfinding.NavigableEntity;
import net.minestom.server.entity.pathfinding.Navigator;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.utils.time.TimeUnit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -17,7 +14,6 @@ import java.time.Duration;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArraySet;
public class EntityCreature extends LivingEntity implements NavigableEntity, EntityAI {
@ -54,13 +50,6 @@ public class EntityCreature extends LivingEntity implements NavigableEntity, Ent
super.update(time);
}
@Override
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
this.navigator.setPathFinder(new HydrazinePathFinder(navigator.getPathingEntity(), instance.getInstanceSpace()));
return super.setInstance(instance, spawnPosition);
}
@Override
public void kill() {
super.kill();

View File

@ -1,14 +1,11 @@
package net.minestom.server.entity.fakeplayer;
import com.extollit.gaming.ai.path.HydrazinePathFinder;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.pathfinding.NavigableEntity;
import net.minestom.server.entity.pathfinding.Navigator;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.player.PlayerSpawnEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.player.FakePlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
@ -17,7 +14,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
@ -121,13 +117,6 @@ public class FakePlayer extends Player implements NavigableEntity {
this.navigator.tick();
}
@Override
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
this.navigator.setPathFinder(new HydrazinePathFinder(navigator.getPathingEntity(), instance.getInstanceSpace()));
return super.setInstance(instance, spawnPosition);
}
@Override
public void updateNewViewer(@NotNull Player player) {
player.getPlayerConnection().sendPacket(getAddPlayerToList());

View File

@ -1,107 +0,0 @@
package net.minestom.server.entity.pathfinding;
import com.extollit.gaming.ai.path.HydrazinePathFinder;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.WorldBorder;
import net.minestom.server.utils.chunk.ChunkUtils;
import java.util.ArrayList;
import java.util.List;
final class HydraPathImpl implements Pathfinder {
private final Entity entity;
final PFPathingEntity pathingEntity;
HydrazinePathFinder pathFinder;
volatile Point pathPosition;
volatile List<Point> path;
HydraPathImpl(Navigator navigator) {
this.entity = navigator.getEntity();
this.pathingEntity = new PFPathingEntity(navigator);
}
@Override
public Point nextPoint(Point currentPoint) {
var path = this.path;
if (path == null || path.isEmpty()) {
return null;
}
// Find the closest point to `pathPosition` in the path that is also closer to `currentPoint`
var closestPoint = path.get(0);
var closestDistance = currentPoint.distance(closestPoint);
int index = 0;
for (var point : path) {
var distance = currentPoint.distance(point);
if (distance < closestDistance) {
closestDistance = distance;
index = path.indexOf(point);
}
}
if (index < path.size() - 1) {
return path.get(index + 1);
} else return null;
}
@Override
public void updatePath(Point point) {
if (point != null && pathPosition != null && point.samePoint(pathPosition)) {
// Tried to set path to the same target position
return;
}
final Instance instance = entity.getInstance();
if (pathFinder == null) {
// Unexpected error
return;
}
this.pathFinder.reset();
if (point == null) {
return;
}
// Can't path with a null instance.
if (instance == null) {
return;
}
// Can't path outside the world border
final WorldBorder worldBorder = instance.getWorldBorder();
if (!worldBorder.isInside(point)) {
return;
}
// Can't path in an unloaded chunk
final Chunk chunk = instance.getChunkAt(point);
if (!ChunkUtils.isLoaded(chunk)) {
return;
}
var path = pathFinder.computePathTo(point.x(), point.y(), point.z());
if (path == null) {
return;
}
List<Point> points = new ArrayList<>();
for (int i = 0; i < path.length(); i++) {
var node = path.at(i);
var coordinates = node.coordinates();
points.add(new Vec(coordinates.x, coordinates.y, coordinates.z));
}
this.pathPosition = point;
this.path = points;
}
@Override
public List<Point> forcePath(Point target) {
updatePath(target);
return path;
}
private void reset() {
this.pathPosition = null;
this.pathFinder.reset();
}
}

View File

@ -1,6 +1,5 @@
package net.minestom.server.entity.pathfinding;
import com.extollit.gaming.ai.path.HydrazinePathFinder;
import net.minestom.server.attribute.Attribute;
import net.minestom.server.collision.CollisionUtils;
import net.minestom.server.coordinate.Point;
@ -20,11 +19,11 @@ import org.jetbrains.annotations.Nullable;
*/
public final class Navigator {
private final Entity entity;
private final HydraPathImpl pathfinder;
private final Pathfinder pathfinder;
public Navigator(@NotNull Entity entity) {
this.entity = entity;
this.pathfinder = new HydraPathImpl(this);
this.pathfinder = new PathfinderImpl(entity);
}
/**
@ -70,7 +69,6 @@ public final class Navigator {
if (entity instanceof LivingEntity && ((LivingEntity) entity).isDead())
return; // No pathfinding tick for dead entities
final Point next = this.pathfinder.nextPoint(entity.getPosition());
//System.out.println("move " + next);
if (next != null) {
moveTowards(next, getAttributeValue(Attribute.MOVEMENT_SPEED));
final double entityY = entity.getPosition().y();
@ -86,23 +84,14 @@ public final class Navigator {
* @return the target pathfinder position, null if there is no one
*/
public @Nullable Point getPathPosition() {
return pathfinder.pathPosition;
return null; // TODO
//return pathfinder.pathPosition;
}
public @NotNull Entity getEntity() {
return entity;
}
@ApiStatus.Internal
public @NotNull PFPathingEntity getPathingEntity() {
return pathfinder.pathingEntity;
}
@ApiStatus.Internal
public void setPathFinder(@Nullable HydrazinePathFinder pathFinder) {
pathfinder.pathFinder = pathFinder;
}
private float getAttributeValue(@NotNull Attribute attribute) {
if (entity instanceof LivingEntity) {
return ((LivingEntity) entity).getAttributeValue(attribute);

View File

@ -1,147 +0,0 @@
package net.minestom.server.entity.pathfinding;
import com.extollit.gaming.ai.path.model.IBlockDescription;
import com.extollit.gaming.ai.path.model.IBlockObject;
import com.extollit.linalg.immutable.AxisAlignedBBox;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import space.vectrix.flare.fastutil.Short2ObjectSyncMap;
@ApiStatus.Internal
public final class PFBlock implements IBlockDescription, IBlockObject {
private static final Short2ObjectSyncMap<PFBlock> BLOCK_DESCRIPTION_MAP = Short2ObjectSyncMap.hashmap();
/**
* Gets the {@link PFBlock} linked to the block state id.
* <p>
* Cache the result if it is not already.
*
* @param block the block
* @return the {@link PFBlock} linked to {@code blockStateId}
*/
public static @NotNull PFBlock get(@NotNull Block block) {
return BLOCK_DESCRIPTION_MAP.computeIfAbsent(block.stateId(), state -> new PFBlock(block));
}
private final Block block;
PFBlock(Block block) {
this.block = block;
}
@Override
public AxisAlignedBBox bounds() {
return new AxisAlignedBBox(
0, 0, 0,
1, 1, 1
);
}
@Override
public boolean isFenceLike() {
// TODO: Use Hitbox
// Return fences, fencegates and walls.
// It just so happens that their namespace IDs contain "fence".
if (block.namespace().asString().contains("fence")) {
return true;
}
// Return all walls
// It just so happens that their namespace IDs all end with "door".
return block.namespace().asString().endsWith("wall");
}
@Override
public boolean isClimbable() {
// Return ladders and vines (including weeping and twisting vines)
// Note that no other Namespace IDs contain "vine" except vines.
return block.compare(Block.LADDER) || block.namespace().asString().contains("vine");
}
@Override
public boolean isDoor() {
// Return all normal doors and trap doors.
// It just so happens that their namespace IDs all end with "door".
return block.namespace().asString().endsWith("door");
}
@Override
public boolean isIntractable() {
// TODO: Interactability of blocks.
return false;
}
@Override
public boolean isImpeding() {
return block.isSolid();
}
@Override
public boolean isFullyBounded() {
// TODO: Use Hitbox (would probably be faster as well)
// Return false for anything that does not have a full hitbox but impedes
// e.g. Anvils, Lilypads, Ladders, Walls, Fences, EnchantmentTables
// Fences & Walls
if (isFenceLike()) {
return false;
}
// Ladders and Vines
if (isClimbable()) {
return false;
}
// All doors/trapdoors.
if (isDoor()) {
return false;
}
if (block.name().startsWith("potted")) {
return false;
}
// Skulls & Heads
if (block.name().contains("skull") || block.name().contains("head")) {
// NOTE: blocks.getName().contains("head") also matches Piston_Head
// I could not find out by documentation if piston_head is fully bounded, I would presume it is NOT.
return false;
}
// Carpets
if (block.name().endsWith("carpet")) {
return false;
}
// Slabs
if (block.name().contains("slab")) {
return false;
}
// Beds
if (block.name().endsWith("bed")) {
return false;
}
// Glass Panes
if (block.name().endsWith("pane")) {
return false;
}
return !Block.CHORUS_FLOWER.compare(block) && !Block.CHORUS_PLANT.compare(block) && !Block.BAMBOO.compare(block)
&& !Block.BAMBOO_SAPLING.compare(block) && !Block.SEA_PICKLE.compare(block)
&& !Block.TURTLE_EGG.compare(block) && !Block.SNOW.compare(block) && !Block.FLOWER_POT.compare(block)
&& !Block.LILY_PAD.compare(block) && !Block.ANVIL.compare(block) && !Block.CHIPPED_ANVIL.compare(block)
&& !Block.DAMAGED_ANVIL.compare(block) && !Block.CAKE.compare(block) && !Block.CACTUS.compare(block)
&& !Block.BREWING_STAND.compare(block) && !Block.LECTERN.compare(block)
&& !Block.DAYLIGHT_DETECTOR.compare(block) && !Block.CAMPFIRE.compare(block)
&& !Block.SOUL_CAMPFIRE.compare(block) && !Block.ENCHANTING_TABLE.compare(block)
&& !Block.CHEST.compare(block) && !Block.ENDER_CHEST.compare(block) && !Block.GRINDSTONE.compare(block)
&& !Block.TRAPPED_CHEST.compare(block) && !Block.SOUL_SAND.compare(block)
&& !Block.SOUL_SOIL.compare(block) && !Block.LANTERN.compare(block) && !Block.COCOA.compare(block)
&& !Block.CONDUIT.compare(block) && !Block.DIRT_PATH.compare(block) && !Block.FARMLAND.compare(block)
&& !Block.END_ROD.compare(block) && !Block.STONECUTTER.compare(block) && !Block.BELL.compare(block);
}
@Override
public boolean isLiquid() {
return block.isLiquid();
}
@Override
public boolean isIncinerating() {
return block == Block.LAVA || block == Block.FIRE || block == Block.SOUL_FIRE;
}
}

View File

@ -1,42 +0,0 @@
package net.minestom.server.entity.pathfinding;
import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList;
import com.extollit.gaming.ai.path.model.IBlockDescription;
import com.extollit.gaming.ai.path.model.IColumnarSpace;
import com.extollit.gaming.ai.path.model.IInstanceSpace;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public final class PFColumnarSpace implements IColumnarSpace {
private final ColumnarOcclusionFieldList occlusionFieldList = new ColumnarOcclusionFieldList(this);
private final PFInstanceSpace instanceSpace;
private final Chunk chunk;
PFColumnarSpace(PFInstanceSpace instanceSpace, Chunk chunk) {
this.instanceSpace = instanceSpace;
this.chunk = chunk;
}
@Override
public IBlockDescription blockAt(int x, int y, int z) {
final Block block = chunk.getBlock(x, y, z);
return PFBlock.get(block);
}
@Override
public int metaDataAt(int x, int y, int z) {
return 0;
}
@Override
public ColumnarOcclusionFieldList occlusionFields() {
return occlusionFieldList;
}
@Override
public IInstanceSpace instance() {
return instanceSpace;
}
}

View File

@ -1,41 +0,0 @@
package net.minestom.server.entity.pathfinding;
import com.extollit.gaming.ai.path.model.IBlockObject;
import com.extollit.gaming.ai.path.model.IColumnarSpace;
import com.extollit.gaming.ai.path.model.IInstanceSpace;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public final class PFInstanceSpace implements IInstanceSpace {
private final Instance instance;
private final Map<Chunk, PFColumnarSpace> chunkSpaceMap = new ConcurrentHashMap<>();
public PFInstanceSpace(Instance instance) {
this.instance = instance;
}
@Override
public IBlockObject blockObjectAt(int x, int y, int z) {
final Block block = instance.getBlock(x, y, z);
return PFBlock.get(block);
}
@Override
public IColumnarSpace columnarSpaceAt(int cx, int cz) {
final Chunk chunk = instance.getChunk(cx, cz);
if (chunk == null) return null;
return chunkSpaceMap.computeIfAbsent(chunk, c -> {
final PFColumnarSpace cs = new PFColumnarSpace(this, c);
c.setColumnarSpace(cs);
return cs;
});
}
public Instance getInstance() {
return instance;
}
}

View File

@ -1,223 +0,0 @@
package net.minestom.server.entity.pathfinding;
import com.extollit.gaming.ai.path.model.Gravitation;
import com.extollit.gaming.ai.path.model.IPathingEntity;
import com.extollit.gaming.ai.path.model.Passibility;
import com.extollit.linalg.immutable.Vec3d;
import net.minestom.server.attribute.Attribute;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.LivingEntity;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@ApiStatus.Internal
public final class PFPathingEntity implements IPathingEntity {
private final Navigator navigator;
private final Entity entity;
private float searchRange;
// Capacities
private boolean fireResistant;
private boolean cautious;
private boolean climber;
private boolean swimmer;
private boolean aquatic;
private boolean avian;
private boolean aquaphobic;
private boolean avoidsDoorways;
private boolean opensDoors;
public PFPathingEntity(Navigator navigator) {
this.navigator = navigator;
this.entity = navigator.getEntity();
this.searchRange = getAttributeValue(Attribute.FOLLOW_RANGE);
}
@Override
public int age() {
return (int) entity.getAliveTicks();
}
@Override
public boolean bound() {
return entity.hasVelocity();
}
@Override
public float searchRange() {
return searchRange;
}
/**
* Changes the search range of the entity
*
* @param searchRange the new entity's search range
*/
public void setSearchRange(float searchRange) {
this.searchRange = searchRange;
}
public boolean isFireResistant() {
return fireResistant;
}
public void setFireResistant(boolean fireResistant) {
this.fireResistant = fireResistant;
}
public boolean isCautious() {
return cautious;
}
public void setCautious(boolean cautious) {
this.cautious = cautious;
}
public boolean isClimber() {
return climber;
}
public void setClimber(boolean climber) {
this.climber = climber;
}
public boolean isSwimmer() {
return swimmer;
}
public void setSwimmer(boolean swimmer) {
this.swimmer = swimmer;
}
public boolean isAquatic() {
return aquatic;
}
public void setAquatic(boolean aquatic) {
this.aquatic = aquatic;
}
public boolean isAvian() {
return avian;
}
public void setAvian(boolean avian) {
this.avian = avian;
}
public boolean isAquaphobic() {
return aquaphobic;
}
public void setAquaphobic(boolean aquaphobic) {
this.aquaphobic = aquaphobic;
}
public boolean isAvoidsDoorways() {
return avoidsDoorways;
}
public void setAvoidsDoorways(boolean avoidsDoorways) {
this.avoidsDoorways = avoidsDoorways;
}
public boolean isOpensDoors() {
return opensDoors;
}
public void setOpensDoors(boolean opensDoors) {
this.opensDoors = opensDoors;
}
@Override
public Capabilities capabilities() {
return new Capabilities() {
@Override
public float speed() {
return getAttributeValue(Attribute.MOVEMENT_SPEED);
}
@Override
public boolean fireResistant() {
return fireResistant;
}
@Override
public boolean cautious() {
return cautious;
}
@Override
public boolean climber() {
return climber;
}
@Override
public boolean swimmer() {
return swimmer;
}
@Override
public boolean aquatic() {
return aquatic;
}
@Override
public boolean avian() {
return avian;
}
@Override
public boolean aquaphobic() {
return aquaphobic;
}
@Override
public boolean avoidsDoorways() {
return avoidsDoorways;
}
@Override
public boolean opensDoors() {
return opensDoors;
}
};
}
@Override
public void moveTo(Vec3d position, Passibility passibility, Gravitation gravitation) {
final Point targetPosition = new Vec(position.x, position.y, position.z);
this.navigator.moveTowards(targetPosition, getAttributeValue(Attribute.MOVEMENT_SPEED));
final double entityY = entity.getPosition().y();
if (entityY < targetPosition.y()) {
this.navigator.jump(1);
}
}
@Override
public Vec3d coordinates() {
final var position = entity.getPosition();
return new Vec3d(position.x(), position.y(), position.z());
}
@Override
public float width() {
return (float) entity.getBoundingBox().width();
}
@Override
public float height() {
return (float) entity.getBoundingBox().height();
}
private float getAttributeValue(@NotNull Attribute attribute) {
if (entity instanceof LivingEntity) {
return ((LivingEntity) entity).getAttributeValue(attribute);
}
return 0f;
}
}

View File

@ -0,0 +1,24 @@
package net.minestom.server.entity.pathfinding;
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.coordinate.Point;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
final class PathfindUtils {
public static boolean isBlocked(@NotNull Point point, @NotNull BoundingBox box,
@NotNull Block.Getter getter, double entityPadding) {
Point relStart = box.relativeStart();
Point relEnd = box.relativeEnd();
relStart = relStart.mul(2, 0, 2).sub(entityPadding, 0, entityPadding);
relEnd = relEnd.mul(2, 0, 2).add(entityPadding, 0, entityPadding);
return getter.getBlock(point.add(relStart.x(), relStart.y(), relStart.z())).isSolid() ||
getter.getBlock(point.add(relStart.x(), relStart.y(), relEnd.z())).isSolid() ||
getter.getBlock(point.add(relStart.x(), relEnd.y(), relStart.z())).isSolid() ||
getter.getBlock(point.add(relStart.x(), relEnd.y(), relEnd.z())).isSolid() ||
getter.getBlock(point.add(relEnd.x(), relStart.y(), relStart.z())).isSolid() ||
getter.getBlock(point.add(relEnd.x(), relStart.y(), relEnd.z())).isSolid() ||
getter.getBlock(point.add(relEnd.x(), relEnd.y(), relStart.z())).isSolid() ||
getter.getBlock(point.add(relEnd.x(), relEnd.y(), relEnd.z())).isSolid();
}
}

View File

@ -0,0 +1,185 @@
package net.minestom.server.entity.pathfinding;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.entity.Entity;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.particle.Particle;
import net.minestom.server.particle.ParticleCreator;
import net.minestom.server.utils.PacketUtils;
import org.jetbrains.annotations.Nullable;
import java.util.*;
final class PathfinderImpl implements Pathfinder {
private static final double DELTA = 0.01;
private final Entity entity;
volatile Point pathPosition;
volatile List<Point> path;
public PathfinderImpl(Entity entity) {
this.entity = entity;
}
@Override
public Point nextPoint(Point currentPoint) {
var path = this.path;
if (path == null || path.isEmpty()) {
return null;
}
// Find the closest point to `pathPosition` in the path that is also closer to `currentPoint`
var closestPoint = path.get(0);
var closestDistance = currentPoint.distance(closestPoint);
int index = 0;
for (var point : path) {
var distance = currentPoint.distance(point);
if (distance < closestDistance) {
closestDistance = distance;
index = path.indexOf(point);
}
}
if (index < path.size() - 1) {
return path.get(index + 1);
} else return null;
}
@Override
public void updatePath(Point target) {
var start = entity.getPosition();
var result = findPath(start, target, 1);
this.path = result.stream().toList();
this.pathPosition = target;
}
@Override
public List<Point> forcePath(Point target) {
updatePath(target);
return path;
}
@Nullable Queue<Point> findPath(Point start, Point goal, double step) {
Comparator<Point> distanceCost = Comparator.comparingDouble(p ->
p.distance(start) +
p.distance(goal) +
getCost(p, p)
);
// The queue of nodes to be evaluated next
Queue<Point> next = new PriorityQueue<>(distanceCost);
Set<Point> nextSet = new HashSet<>();
next.add(start);
// The set of nodes already evaluated
Set<Point> closedSet = new HashSet<>();
// The map from each node to its parent node
Map<Point, Point> cameFrom = new HashMap<>();
while (!next.isEmpty()) {
Point current = next.remove();
nextSet.remove(current);
// TODO: Remove this debug
ServerPacket packet = ParticleCreator.createParticlePacket(
Particle.FLAME,
current.x(), current.y(), current.z(),
0, 0, 0,
1
);
PacketUtils.sendGroupedPacket(MinecraftServer.getConnectionManager().getOnlinePlayers(), packet);
// Return if the current node is the goal
if (current.distance(goal) - DELTA <= step) {
return reconstructPath(cameFrom, current);
}
// Else, look at the neighbors
for (Point neighbor : neighbors(current, step)) {
// If the neighbor is already evaluated, or scheduled for evaluation, skip it
if (closedSet.contains(neighbor) || nextSet.contains(neighbor)) {
continue;
}
// If the neighbor is not walkable, skip it
if (isBlocked(neighbor)) {
continue;
}
// Else, add it to the queue
next.add(neighbor);
nextSet.add(neighbor);
cameFrom.put(neighbor, current);
}
// Mark the current node as visited
closedSet.add(current);
}
// No path found
return null;
}
private static Queue<Point> reconstructPath(Map<Point, Point> cameFrom, Point current) {
Deque<Point> path = new ArrayDeque<>();
path.add(current);
while (cameFrom.containsKey(current)) {
current = cameFrom.get(current);
path.addFirst(current);
}
return path;
}
private static Point[] neighbors(Point point, double step) {
return new Point[]{
// Direct neighbors
point.add(step, 0, 0),
point.add(-step, 0, 0),
point.add(0, step, 0),
point.add(0, -step, 0),
point.add(0, 0, step),
point.add(0, 0, -step),
// Diagonal neighbors
point.add(step, step, 0),
point.add(-step, -step, 0),
point.add(step, -step, 0),
point.add(-step, step, 0),
point.add(0, step, step),
point.add(0, -step, -step),
point.add(step, 0, step),
point.add(-step, 0, -step),
// Diagonal Diagonal neighbors
point.add(step, step, step),
point.add(-step, -step, -step),
point.add(step, -step, -step),
point.add(-step, step, -step),
point.add(step, step, -step),
point.add(-step, -step, step),
point.add(step, -step, step),
point.add(-step, step, step)
};
}
public double getCost(Point from, Point to) {
// TODO: Implement line intersection algorithm to determine the cost
// The current algorithm is flawed and may tell the navigator to move through very
// specific corners that are not actually possible
Instance instance = entity.getInstance();
Objects.requireNonNull(instance, "The navigator must be in an instance while pathfinding.");
Block block = instance.getBlock(to);
if (block.isSolid()) {
return Double.POSITIVE_INFINITY;
}
return block.registry().speedFactor();
}
public boolean isBlocked(Point point) {
Instance instance = entity.getInstance();
Objects.requireNonNull(instance, "The navigator must be in an instance while pathfinding.");
return PathfindUtils.isBlocked(point, entity.getBoundingBox(), instance, 0.1);
}
}

View File

@ -5,7 +5,6 @@ import net.minestom.server.Viewable;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
import net.minestom.server.instance.block.Block;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
import net.minestom.server.snapshot.Snapshotable;
@ -54,9 +53,6 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
protected volatile boolean loaded = true;
private final ChunkView viewers;
// Path finding
protected PFColumnarSpace columnarSpace;
// Data
private final MutableNBTCompound nbt = new MutableNBTCompound();
@ -245,15 +241,6 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
this.readOnly = readOnly;
}
/**
* Changes this chunk columnar space.
*
* @param columnarSpace the new columnar space
*/
public void setColumnarSpace(PFColumnarSpace columnarSpace) {
this.columnarSpace = columnarSpace;
}
/**
* Used to verify if the chunk should still be kept in memory.
*

View File

@ -1,12 +1,10 @@
package net.minestom.server.instance;
import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Point;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.pathfinding.PFBlock;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockHandler;
import net.minestom.server.network.packet.server.CachedPacket;
@ -63,12 +61,6 @@ public class DynamicChunk extends Chunk {
this.lastChange = System.currentTimeMillis();
this.chunkCache.invalidate();
this.lightCache.invalidate();
// Update pathfinder
if (columnarSpace != null) {
final ColumnarOcclusionFieldList columnarOcclusionFieldList = columnarSpace.occlusionFields();
final var blockDescription = PFBlock.get(block);
columnarOcclusionFieldList.onBlockChanged(x, y, z, blockDescription, 0);
}
Section section = getSectionAt(y);
section.blockPalette()
.set(toSectionRelativeCoordinate(x), toSectionRelativeCoordinate(y), toSectionRelativeCoordinate(z), block.stateId());

View File

@ -11,7 +11,6 @@ import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.ExperienceOrb;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.pathfinding.PFInstanceSpace;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.instance.InstanceTickEvent;
import net.minestom.server.instance.block.Block;
@ -93,9 +92,6 @@ public abstract class Instance implements Block.Getter, Block.Setter, Tickable,
// the explosion supplier
private ExplosionSupplier explosionSupplier;
// Pathfinder
private final PFInstanceSpace instanceSpace = new PFInstanceSpace(this);
// Adventure
private final Pointers pointers;
@ -676,18 +672,6 @@ public abstract class Instance implements Block.Getter, Block.Setter, Tickable,
this.explosionSupplier = supplier;
}
/**
* Gets the instance space.
* <p>
* Used by the pathfinder for entities.
*
* @return the instance space
*/
@ApiStatus.Internal
public @NotNull PFInstanceSpace getInstanceSpace() {
return instanceSpace;
}
@Override
public @NotNull Pointers pointers() {
return this.pointers;