diff --git a/build.gradle.kts b/build.gradle.kts index 2a1e2df30..0ba517189 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6852175fa..fc6bf45fd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/src/main/java/net/minestom/server/entity/EntityCreature.java b/src/main/java/net/minestom/server/entity/EntityCreature.java index fdbf64d3d..f59bc4dd1 100644 --- a/src/main/java/net/minestom/server/entity/EntityCreature.java +++ b/src/main/java/net/minestom/server/entity/EntityCreature.java @@ -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 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(); diff --git a/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java b/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java index 0e8b3de1b..ea30da0e2 100644 --- a/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java +++ b/src/main/java/net/minestom/server/entity/fakeplayer/FakePlayer.java @@ -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 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()); diff --git a/src/main/java/net/minestom/server/entity/pathfinding/HydraPathImpl.java b/src/main/java/net/minestom/server/entity/pathfinding/HydraPathImpl.java deleted file mode 100644 index 99d9f00f6..000000000 --- a/src/main/java/net/minestom/server/entity/pathfinding/HydraPathImpl.java +++ /dev/null @@ -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 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 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 forcePath(Point target) { - updatePath(target); - return path; - } - - private void reset() { - this.pathPosition = null; - this.pathFinder.reset(); - } -} diff --git a/src/main/java/net/minestom/server/entity/pathfinding/Navigator.java b/src/main/java/net/minestom/server/entity/pathfinding/Navigator.java index ee7bad96e..aa1885e24 100644 --- a/src/main/java/net/minestom/server/entity/pathfinding/Navigator.java +++ b/src/main/java/net/minestom/server/entity/pathfinding/Navigator.java @@ -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); diff --git a/src/main/java/net/minestom/server/entity/pathfinding/PFBlock.java b/src/main/java/net/minestom/server/entity/pathfinding/PFBlock.java deleted file mode 100644 index 7c286aaf1..000000000 --- a/src/main/java/net/minestom/server/entity/pathfinding/PFBlock.java +++ /dev/null @@ -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 BLOCK_DESCRIPTION_MAP = Short2ObjectSyncMap.hashmap(); - - /** - * Gets the {@link PFBlock} linked to the block state id. - *

- * 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; - } - -} diff --git a/src/main/java/net/minestom/server/entity/pathfinding/PFColumnarSpace.java b/src/main/java/net/minestom/server/entity/pathfinding/PFColumnarSpace.java deleted file mode 100644 index 3ef7e6e00..000000000 --- a/src/main/java/net/minestom/server/entity/pathfinding/PFColumnarSpace.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/net/minestom/server/entity/pathfinding/PFInstanceSpace.java b/src/main/java/net/minestom/server/entity/pathfinding/PFInstanceSpace.java deleted file mode 100644 index d59126f9c..000000000 --- a/src/main/java/net/minestom/server/entity/pathfinding/PFInstanceSpace.java +++ /dev/null @@ -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 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; - } -} diff --git a/src/main/java/net/minestom/server/entity/pathfinding/PFPathingEntity.java b/src/main/java/net/minestom/server/entity/pathfinding/PFPathingEntity.java deleted file mode 100644 index 954706d87..000000000 --- a/src/main/java/net/minestom/server/entity/pathfinding/PFPathingEntity.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/net/minestom/server/entity/pathfinding/PathfindUtils.java b/src/main/java/net/minestom/server/entity/pathfinding/PathfindUtils.java new file mode 100644 index 000000000..aceca1df4 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/pathfinding/PathfindUtils.java @@ -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(); + } +} diff --git a/src/main/java/net/minestom/server/entity/pathfinding/PathfinderImpl.java b/src/main/java/net/minestom/server/entity/pathfinding/PathfinderImpl.java new file mode 100644 index 000000000..bcf39bdda --- /dev/null +++ b/src/main/java/net/minestom/server/entity/pathfinding/PathfinderImpl.java @@ -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 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 forcePath(Point target) { + updatePath(target); + return path; + } + + @Nullable Queue findPath(Point start, Point goal, double step) { + Comparator distanceCost = Comparator.comparingDouble(p -> + p.distance(start) + + p.distance(goal) + + getCost(p, p) + ); + // The queue of nodes to be evaluated next + Queue next = new PriorityQueue<>(distanceCost); + Set nextSet = new HashSet<>(); + next.add(start); + + // The set of nodes already evaluated + Set closedSet = new HashSet<>(); + + // The map from each node to its parent node + Map 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 reconstructPath(Map cameFrom, Point current) { + Deque 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); + } +} diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index be37c4904..d0ab37fa6 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -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. * diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index 6b65363ab..d8165d558 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -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()); diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index e8902d28b..015fea1fc 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -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. - *

- * 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;