!refactor: reimplemented movement target
This commit is contained in:
parent
c0a6b80742
commit
e50ee8a3d6
@ -3,215 +3,54 @@ package ru.dragonestia.msb3.api.ai;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import net.minestom.server.coordinate.Point;
|
import ru.dragonestia.msb3.api.ai.movement.target.MovementTarget;
|
||||||
import net.minestom.server.coordinate.Pos;
|
import ru.dragonestia.msb3.api.ai.movement.follower.GroundMovementFollower;
|
||||||
import net.minestom.server.coordinate.Vec;
|
import ru.dragonestia.msb3.api.ai.movement.follower.MovementFollower;
|
||||||
import net.minestom.server.entity.attribute.Attribute;
|
|
||||||
import ru.dragonestia.msb3.api.ai.movement.GroundMovementFollower;
|
|
||||||
import ru.dragonestia.msb3.api.ai.movement.MovementFollower;
|
|
||||||
import ru.dragonestia.msb3.api.ai.navigator.*;
|
|
||||||
import ru.dragonestia.msb3.api.ai.navigator.follower.GroundNodeFollower;
|
|
||||||
import ru.dragonestia.msb3.api.ai.navigator.follower.NodeFollower;
|
|
||||||
import ru.dragonestia.msb3.api.ai.navigator.node.GroundNodeGenerator;
|
import ru.dragonestia.msb3.api.ai.navigator.node.GroundNodeGenerator;
|
||||||
import ru.dragonestia.msb3.api.ai.navigator.node.NodeGenerator;
|
import ru.dragonestia.msb3.api.ai.navigator.node.NodeGenerator;
|
||||||
import ru.dragonestia.msb3.api.debug.DebugMessage;
|
|
||||||
import ru.dragonestia.msb3.api.entity.EntityAI;
|
import ru.dragonestia.msb3.api.entity.EntityAI;
|
||||||
import ru.dragonestia.msb3.api.util.UncheckedRunnable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс для управления поведением сущности
|
||||||
|
*/
|
||||||
@Log4j2
|
@Log4j2
|
||||||
@Getter
|
@Getter
|
||||||
public class AI {
|
public class AI {
|
||||||
|
|
||||||
private final EntityAI entity;
|
private final EntityAI entity;
|
||||||
private DestinationPoint destinationData;
|
|
||||||
@Setter private NodeGenerator nodeGenerator = new GroundNodeGenerator();
|
@Setter private NodeGenerator nodeGenerator = new GroundNodeGenerator();
|
||||||
@Setter private NodeFollower nodeFollower;
|
|
||||||
private final MovementFollower movementFollower;
|
private final MovementFollower movementFollower;
|
||||||
@Getter private final Actor actor;
|
@Getter private final Actor actor;
|
||||||
@Setter private Point destinationPoint;
|
@Getter private MovementTarget movementTarget;
|
||||||
|
|
||||||
public AI(EntityAI entity) {
|
public AI(EntityAI entity) {
|
||||||
this.entity = Objects.requireNonNull(entity, "AI is null");
|
this.entity = Objects.requireNonNull(entity, "AI is null");
|
||||||
nodeFollower = new GroundNodeFollower(entity);
|
|
||||||
movementFollower = new GroundMovementFollower(entity);
|
movementFollower = new GroundMovementFollower(entity);
|
||||||
actor = new Actor(entity);
|
actor = new Actor(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathState getCurrentPathState() {
|
public void setMovementTarget(MovementTarget movementTarget) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (destinationData == null) return PathState.NONE;
|
if (this.movementTarget != null) {
|
||||||
return destinationData.path().getState();
|
this.movementTarget.cancel();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<PathState> setPathTo(Point point) {
|
|
||||||
var bb = entity.getBoundingBox();
|
|
||||||
var centerToCorner = Math.sqrt(bb.width() * bb.width() + bb.depth() * bb.depth()) / 2;
|
|
||||||
return setPathTo(point, centerToCorner);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<PathState> setPathTo(Point point, double arrivalDistance) {
|
|
||||||
return setPathTo(point, arrivalDistance, 50, 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<PathState> setPathTo(Point point, double arrivalDistance, double maxDistance, double pathVariance) {
|
|
||||||
Objects.requireNonNull(point, "Point is null");
|
|
||||||
var instance = Objects.requireNonNull(entity.getInstance(), "Entity is not spawned");
|
|
||||||
if (!instance.getWorldBorder().inBounds(point))
|
|
||||||
throw new IllegalStateException("Destination point is not in world bounds");
|
|
||||||
if (!instance.isChunkLoaded(point))
|
|
||||||
throw new IllegalStateException("Chunk is not loaded for destination point");
|
|
||||||
|
|
||||||
|
|
||||||
synchronized (this) {
|
|
||||||
// Завершаем активное следование к точке
|
|
||||||
if (destinationData != null) {
|
|
||||||
var data = destinationData;
|
|
||||||
destinationData = null;
|
|
||||||
|
|
||||||
UncheckedRunnable.runIgnoreException(() -> data.future().complete(PathState.TERMINATED));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Игрок находится рядом с точкой, куда нужно прийти
|
this.movementTarget = movementTarget;
|
||||||
if (entity.getDistanceSquared(point) <= arrivalDistance * arrivalDistance) {
|
|
||||||
return CompletableFuture.completedFuture(PathState.COMPLETED);
|
if (movementTarget != null) {
|
||||||
|
movementTarget.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка находится ли точка слишком далеко
|
|
||||||
if (entity.getDistanceSquared(point) > Math.pow(maxDistance - arrivalDistance / 2, 2)) {
|
|
||||||
return CompletableFuture.completedFuture(PathState.TOO_FAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Начинаем просчет пути до новой точки
|
|
||||||
var path = PathGenerator.generate(instance,
|
|
||||||
entity.getPosition(),
|
|
||||||
point,
|
|
||||||
arrivalDistance,
|
|
||||||
maxDistance,
|
|
||||||
pathVariance,
|
|
||||||
entity.getBoundingBox(),
|
|
||||||
entity.isOnGround(),
|
|
||||||
nodeGenerator);
|
|
||||||
|
|
||||||
// Если на время просчета пути уже известен результат действия
|
|
||||||
if (path.getState().isCompleted()) {
|
|
||||||
return CompletableFuture.completedFuture(path.getState());
|
|
||||||
}
|
|
||||||
|
|
||||||
return (destinationData = new DestinationPoint(Vec.fromPoint(point), arrivalDistance, path, new CompletableFuture<>()))
|
|
||||||
.future().thenApply(result -> {
|
|
||||||
resetDestinationData();
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tick(long delta) {
|
public void tick(long delta) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (entity.isDead()) return;
|
|
||||||
|
|
||||||
var action = actor.getCurrentAction();
|
var action = actor.getCurrentAction();
|
||||||
if (action != null) action.tick(actor, entity, delta);
|
if (action != null) action.tick(actor, entity, delta);
|
||||||
|
|
||||||
if (destinationData != null) tickNavigator();
|
if (movementTarget != null) movementTarget.tick();
|
||||||
if (destinationPoint != null) tickNewNavigator();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tickNewNavigator() {
|
|
||||||
if (checkDestinationPointCompleted()) return;
|
|
||||||
|
|
||||||
var speed = Math.min(getMovementSpeed(), Math.sqrt(movementFollower.squaredDistance(destinationPoint)));
|
|
||||||
var prevPosition = entity.getPosition();
|
|
||||||
movementFollower.moveTo(destinationPoint, speed, destinationPoint);
|
|
||||||
var movementDistance = movementFollower.squaredDistance(prevPosition);
|
|
||||||
if (movementDistance == 0) {
|
|
||||||
// Deadlocked?
|
|
||||||
}
|
|
||||||
|
|
||||||
checkDestinationPointCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkDestinationPointCompleted() {
|
|
||||||
double distSquared = movementFollower.squaredDistance(destinationPoint);
|
|
||||||
if (distSquared < 0.01 * 0.01) {
|
|
||||||
destinationPoint = null;
|
|
||||||
DebugMessage.broadcast("Destination point completed");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tickNavigator() {
|
|
||||||
if (entity.getPosition().distance(destinationData.position()) < destinationData.arrivalDistance()) {
|
|
||||||
var future = destinationData.future();
|
|
||||||
UncheckedRunnable.runIgnoreException(() -> future.complete(PathState.COMPLETED));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var path = destinationData.path();
|
|
||||||
var currentTarget = path.getCurrent();
|
|
||||||
var nextTarget = path.getNext();
|
|
||||||
|
|
||||||
if (currentTarget == null || path.getCurrentType() == PathNode.Type.REPATH || path.getCurrentType() == null) {
|
|
||||||
recalculatePath();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextTarget == null) nextTarget = destinationData.position();
|
|
||||||
|
|
||||||
boolean nextIsRePath = nextTarget.sameBlock(Pos.ZERO);
|
|
||||||
nodeFollower.moveTowards(currentTarget, nodeFollower.movementSpeed(), nextIsRePath ? currentTarget : nextTarget);
|
|
||||||
|
|
||||||
// TODO: исправить баг, в данном месте, когда Entity не движется вообще. Сделать хорошую проверку на застревание
|
|
||||||
// TODO: присутствует баг, что если как-то помешать Entity следовать до точки, например поставить перед ним блок, то он перестанет идти
|
|
||||||
// TODO: Бля, этот поиск пути говнище полное! Он не учитывает ни заборы, ни полублоки, ни ковры и остальные неполноценные блоки. Трава - это вообще пиздец.
|
|
||||||
// TODO: использовать CollisionUtils для просчета поиска путей
|
|
||||||
|
|
||||||
if (nodeFollower.isAtPoint(currentTarget)) path.next();
|
|
||||||
else if (path.getCurrentType() == PathNode.Type.JUMP) nodeFollower.jump(currentTarget, nextTarget);
|
|
||||||
checkFinishedPath(path, destinationData.future());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void recalculatePath() {
|
|
||||||
if (destinationData == null) return;
|
|
||||||
|
|
||||||
var originalPath = destinationData.path();
|
|
||||||
var newPath = PathGenerator.generate(entity.getInstance(),
|
|
||||||
entity.getPosition(),
|
|
||||||
destinationData.position(),
|
|
||||||
destinationData.arrivalDistance(), originalPath.getMaxDistance(),
|
|
||||||
originalPath.getPathVariance(), entity.getBoundingBox(), entity.isOnGround(), nodeGenerator);
|
|
||||||
|
|
||||||
destinationData = new DestinationPoint(destinationData.position(), destinationData.arrivalDistance(), newPath, destinationData.future());
|
|
||||||
checkFinishedPath(newPath, destinationData.future());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkFinishedPath(Path path, CompletableFuture<PathState> future) {
|
|
||||||
var state = path.getState();
|
|
||||||
if (state.isCompleted()) {
|
|
||||||
UncheckedRunnable.runIgnoreException(() -> future.complete(state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetDestinationData() {
|
|
||||||
synchronized (this) {
|
|
||||||
if (destinationData == null) return;
|
|
||||||
|
|
||||||
var future = destinationData.future();
|
|
||||||
destinationData = null;
|
|
||||||
if (!future.isDone()) future.complete(PathState.TERMINATED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCompleted() {
|
|
||||||
return getCurrentPathState().isCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getMovementSpeed() {
|
|
||||||
return entity.getAttribute(Attribute.MOVEMENT_SPEED).getValue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package ru.dragonestia.msb3.api.ai;
|
||||||
|
|
||||||
|
public interface ArrivalResultListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправляет завершенное состояние пути. Может быть положительным (моб дошел до нужной точки), либо негативным (потерялся)
|
||||||
|
* @param resultState Состояние полученное в конце пути
|
||||||
|
*/
|
||||||
|
void result(FollowerState resultState);
|
||||||
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
package ru.dragonestia.msb3.api.ai.navigator;
|
package ru.dragonestia.msb3.api.ai;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public enum PathState {
|
public enum FollowerState {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Путь не задан.
|
* Путь не задан.
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package ru.dragonestia.msb3.api.ai;
|
||||||
|
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public record FollowerTarget(Point point, ArrivalResultListener callback) {}
|
||||||
@ -5,7 +5,8 @@ import lombok.Setter;
|
|||||||
import net.minestom.server.coordinate.Pos;
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.coordinate.Vec;
|
import net.minestom.server.coordinate.Vec;
|
||||||
import ru.dragonestia.msb3.api.ai.Actor;
|
import ru.dragonestia.msb3.api.ai.Actor;
|
||||||
import ru.dragonestia.msb3.api.ai.navigator.PathState;
|
import ru.dragonestia.msb3.api.ai.FollowerState;
|
||||||
|
import ru.dragonestia.msb3.api.ai.movement.target.MovementTarget;
|
||||||
import ru.dragonestia.msb3.api.entity.EntityAI;
|
import ru.dragonestia.msb3.api.entity.EntityAI;
|
||||||
import ru.dragonestia.msb3.api.scheduler.Scheduler;
|
import ru.dragonestia.msb3.api.scheduler.Scheduler;
|
||||||
|
|
||||||
@ -41,7 +42,8 @@ public class PatrolAction implements Action {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop(Actor actor, EntityAI entity) {
|
public void stop(Actor actor, EntityAI entity) {
|
||||||
entity.getAi().resetDestinationData();
|
var ai = entity.getAi();
|
||||||
|
if (ai.getMovementTarget() != null) ai.getMovementTarget().cancel();
|
||||||
this.entity = null;
|
this.entity = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,25 +83,24 @@ public class PatrolAction implements Action {
|
|||||||
|
|
||||||
private void walkToNextPos(EntityAI entity) {
|
private void walkToNextPos(EntityAI entity) {
|
||||||
var nextPos = useNextPos();
|
var nextPos = useNextPos();
|
||||||
var navigator = entity.getAi();
|
var ai = entity.getAi();
|
||||||
|
|
||||||
navigator.setPathTo(nextPos, destinationRadius)
|
ai.setMovementTarget(MovementTarget.withPathFinder(entity, nextPos, result -> {
|
||||||
.thenAccept(result -> {
|
if (result == FollowerState.TERMINATED) return;
|
||||||
if (result == PathState.TERMINATED) return;
|
if (result == FollowerState.TOO_FAR) {
|
||||||
if (result == PathState.TOO_FAR) {
|
Scheduler.ofEntity(entity).delayedTask(() -> {
|
||||||
Scheduler.ofEntity(entity).delayedTask(() -> {
|
entity.teleport(Pos.fromPoint(nextPos))
|
||||||
entity.teleport(Pos.fromPoint(nextPos))
|
.thenRun(() -> walkToNextPos(entity));
|
||||||
.thenRun(() -> walkToNextPos(entity));
|
}, Duration.ofSeconds(1));
|
||||||
}, Duration.ofSeconds(1));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (result == FollowerState.DEADLOCKED) {
|
||||||
if (result == PathState.DEADLOCKED) {
|
entity.teleport(Pos.fromPoint(nextPos))
|
||||||
entity.teleport(Pos.fromPoint(nextPos))
|
.thenRun(() -> walkToNextPos(entity));
|
||||||
.thenRun(() -> walkToNextPos(entity));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
walkToNextPos(entity);
|
walkToNextPos(entity);
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package ru.dragonestia.msb3.api.ai.movement;
|
package ru.dragonestia.msb3.api.ai.movement.follower;
|
||||||
|
|
||||||
import net.minestom.server.collision.CollisionUtils;
|
import net.minestom.server.collision.CollisionUtils;
|
||||||
import net.minestom.server.collision.PhysicsResult;
|
import net.minestom.server.collision.PhysicsResult;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package ru.dragonestia.msb3.api.ai.movement;
|
package ru.dragonestia.msb3.api.ai.movement.follower;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
package ru.dragonestia.msb3.api.ai.movement.target;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import ru.dragonestia.msb3.api.ai.ArrivalResultListener;
|
||||||
|
import ru.dragonestia.msb3.api.ai.FollowerState;
|
||||||
|
import ru.dragonestia.msb3.api.entity.EntityAI;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class MovementTarget {
|
||||||
|
|
||||||
|
private final EntityAI entity;
|
||||||
|
private final Point destinationPoint;
|
||||||
|
private final ArrivalResultListener onArrive;
|
||||||
|
private boolean started = false;
|
||||||
|
private boolean completed = false;
|
||||||
|
|
||||||
|
public final void start() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (started) return;
|
||||||
|
completed = false;
|
||||||
|
started = true;
|
||||||
|
|
||||||
|
start0();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void start0() {}
|
||||||
|
|
||||||
|
public final void tick() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (!started) return;
|
||||||
|
|
||||||
|
tick0();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void tick0() {}
|
||||||
|
|
||||||
|
public final void cancel() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (!started || completed) return;
|
||||||
|
started = false;
|
||||||
|
completed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel0()) {
|
||||||
|
complete(FollowerState.TERMINATED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean cancel0() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized final void complete(FollowerState state) {
|
||||||
|
if (!state.isCompleted()) {
|
||||||
|
throw new IllegalStateException("Invalid state taken for completed movement. Taken: %s".formatted(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
completed = true;
|
||||||
|
entity.getAi().setMovementTarget(null);
|
||||||
|
onArrive.result(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MovementTarget justMoveTo(EntityAI entity, Point destination, ArrivalResultListener onArrive) {
|
||||||
|
return new WithoutPathFinderMovementTarget(entity, destination, onArrive);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MovementTarget withPathFinder(EntityAI entity, Point destination, ArrivalResultListener onArrive) {
|
||||||
|
return new WithPathFinderMovementTarget(entity, destination, onArrive);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
package ru.dragonestia.msb3.api.ai.movement.target;
|
||||||
|
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import ru.dragonestia.msb3.api.ai.ArrivalResultListener;
|
||||||
|
import ru.dragonestia.msb3.api.entity.EntityAI;
|
||||||
|
|
||||||
|
public class WithPathFinderMovementTarget extends MovementTarget{
|
||||||
|
|
||||||
|
WithPathFinderMovementTarget(EntityAI entity, Point destinationPoint, ArrivalResultListener onArrive) {
|
||||||
|
super(entity, destinationPoint, onArrive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: implement it
|
||||||
|
public CompletableFuture<FollowerState> setPathTo(Point point) {
|
||||||
|
var bb = entity.getBoundingBox();
|
||||||
|
var centerToCorner = Math.sqrt(bb.width() * bb.width() + bb.depth() * bb.depth()) / 2;
|
||||||
|
return setPathTo(point, centerToCorner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<FollowerState> setPathTo(Point point, double arrivalDistance) {
|
||||||
|
return setPathTo(point, arrivalDistance, 50, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<FollowerState> setPathTo(Point point, double arrivalDistance, double maxDistance, double pathVariance) {
|
||||||
|
Objects.requireNonNull(point, "Point is null");
|
||||||
|
var instance = Objects.requireNonNull(entity.getInstance(), "Entity is not spawned");
|
||||||
|
if (!instance.getWorldBorder().inBounds(point))
|
||||||
|
throw new IllegalStateException("Destination point is not in world bounds");
|
||||||
|
if (!instance.isChunkLoaded(point))
|
||||||
|
throw new IllegalStateException("Chunk is not loaded for destination point");
|
||||||
|
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
// Завершаем активное следование к точке
|
||||||
|
if (destinationData != null) {
|
||||||
|
var data = destinationData;
|
||||||
|
destinationData = null;
|
||||||
|
|
||||||
|
UncheckedRunnable.runIgnoreException(() -> data.future().complete(FollowerState.TERMINATED));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Игрок находится рядом с точкой, куда нужно прийти
|
||||||
|
if (entity.getDistanceSquared(point) <= arrivalDistance * arrivalDistance) {
|
||||||
|
return CompletableFuture.completedFuture(FollowerState.COMPLETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка находится ли точка слишком далеко
|
||||||
|
if (entity.getDistanceSquared(point) > Math.pow(maxDistance - arrivalDistance / 2, 2)) {
|
||||||
|
return CompletableFuture.completedFuture(FollowerState.TOO_FAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Начинаем просчет пути до новой точки
|
||||||
|
var path = PathGenerator.generate(instance,
|
||||||
|
entity.getPosition(),
|
||||||
|
point,
|
||||||
|
arrivalDistance,
|
||||||
|
maxDistance,
|
||||||
|
pathVariance,
|
||||||
|
entity.getBoundingBox(),
|
||||||
|
entity.isOnGround(),
|
||||||
|
nodeGenerator);
|
||||||
|
|
||||||
|
// Если на время просчета пути уже известен результат действия
|
||||||
|
if (path.getState().isCompleted()) {
|
||||||
|
return CompletableFuture.completedFuture(path.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (destinationData = new DestinationPoint(Vec.fromPoint(point), arrivalDistance, path, new CompletableFuture<>()))
|
||||||
|
.future().thenApply(result -> {
|
||||||
|
resetDestinationData();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tickNavigator() {
|
||||||
|
if (entity.getPosition().distance(destinationData.position()) < destinationData.arrivalDistance()) {
|
||||||
|
var future = destinationData.future();
|
||||||
|
UncheckedRunnable.runIgnoreException(() -> future.complete(FollowerState.COMPLETED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = destinationData.path();
|
||||||
|
var currentTarget = path.getCurrent();
|
||||||
|
var nextTarget = path.getNext();
|
||||||
|
|
||||||
|
if (currentTarget == null || path.getCurrentType() == PathNode.Type.REPATH || path.getCurrentType() == null) {
|
||||||
|
recalculatePath();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextTarget == null) nextTarget = destinationData.position();
|
||||||
|
|
||||||
|
boolean nextIsRePath = nextTarget.sameBlock(Pos.ZERO);
|
||||||
|
nodeFollower.moveTowards(currentTarget, nodeFollower.movementSpeed(), nextIsRePath ? currentTarget : nextTarget);
|
||||||
|
|
||||||
|
// TODO: исправить баг, в данном месте, когда Entity не движется вообще. Сделать хорошую проверку на застревание
|
||||||
|
// TODO: присутствует баг, что если как-то помешать Entity следовать до точки, например поставить перед ним блок, то он перестанет идти
|
||||||
|
// TODO: Бля, этот поиск пути говнище полное! Он не учитывает ни заборы, ни полублоки, ни ковры и остальные неполноценные блоки. Трава - это вообще пиздец.
|
||||||
|
// TODO: использовать CollisionUtils для просчета поиска путей
|
||||||
|
|
||||||
|
if (nodeFollower.isAtPoint(currentTarget)) path.next();
|
||||||
|
else if (path.getCurrentType() == PathNode.Type.JUMP) nodeFollower.jump(currentTarget, nextTarget);
|
||||||
|
checkFinishedPath(path, destinationData.future());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recalculatePath() {
|
||||||
|
if (destinationData == null) return;
|
||||||
|
|
||||||
|
var originalPath = destinationData.path();
|
||||||
|
var newPath = PathGenerator.generate(entity.getInstance(),
|
||||||
|
entity.getPosition(),
|
||||||
|
destinationData.position(),
|
||||||
|
destinationData.arrivalDistance(), originalPath.getMaxDistance(),
|
||||||
|
originalPath.getPathVariance(), entity.getBoundingBox(), entity.isOnGround(), nodeGenerator);
|
||||||
|
|
||||||
|
destinationData = new DestinationPoint(destinationData.position(), destinationData.arrivalDistance(), newPath, destinationData.future());
|
||||||
|
checkFinishedPath(newPath, destinationData.future());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkFinishedPath(Path path, CompletableFuture<FollowerState> future) {
|
||||||
|
var state = path.getState();
|
||||||
|
if (state.isCompleted()) {
|
||||||
|
UncheckedRunnable.runIgnoreException(() -> future.complete(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetDestinationData() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (destinationData == null) return;
|
||||||
|
|
||||||
|
var future = destinationData.future();
|
||||||
|
destinationData = null;
|
||||||
|
if (!future.isDone()) future.complete(FollowerState.TERMINATED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package ru.dragonestia.msb3.api.ai.movement.target;
|
||||||
|
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import ru.dragonestia.msb3.api.ai.ArrivalResultListener;
|
||||||
|
import ru.dragonestia.msb3.api.ai.FollowerState;
|
||||||
|
import ru.dragonestia.msb3.api.ai.movement.follower.MovementFollower;
|
||||||
|
import ru.dragonestia.msb3.api.entity.EntityAI;
|
||||||
|
|
||||||
|
public class WithoutPathFinderMovementTarget extends MovementTarget {
|
||||||
|
|
||||||
|
private MovementFollower movementFollower;
|
||||||
|
|
||||||
|
WithoutPathFinderMovementTarget(EntityAI entity, Point destinationPoint, ArrivalResultListener onArrive) {
|
||||||
|
super(entity, destinationPoint, onArrive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void start0() {
|
||||||
|
movementFollower = getEntity().getMovementFollower();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void tick0() {
|
||||||
|
if (checkDestinationPointCompleted()) return;
|
||||||
|
|
||||||
|
var speed = Math.min(getEntity().getMovementSpeed(), Math.sqrt(movementFollower.squaredDistance(getDestinationPoint())));
|
||||||
|
var prevPosition = getEntity().getPosition();
|
||||||
|
movementFollower.moveTo(getDestinationPoint(), speed, getDestinationPoint());
|
||||||
|
var movementDistance = movementFollower.squaredDistance(prevPosition);
|
||||||
|
if (movementDistance == 0) { // TODO: catch deadlock condition
|
||||||
|
// Deadlocked?
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDestinationPointCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkDestinationPointCompleted() {
|
||||||
|
double distSquared = movementFollower.squaredDistance(getDestinationPoint());
|
||||||
|
if (distSquared < 0.01 * 0.01) {
|
||||||
|
complete(FollowerState.COMPLETED);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package ru.dragonestia.msb3.api.ai.navigator;
|
|
||||||
|
|
||||||
import net.minestom.server.coordinate.Vec;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
public record DestinationPoint(Vec position, double arrivalDistance, Path path, CompletableFuture<PathState> future) {}
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package ru.dragonestia.msb3.api.ai.navigator;
|
||||||
|
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
|
||||||
|
public interface Navigator {
|
||||||
|
|
||||||
|
NavigatorPath findPath(Point point);
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
package ru.dragonestia.msb3.api.ai.navigator;
|
||||||
|
|
||||||
|
public record NavigatorPath() {
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import lombok.Getter;
|
|||||||
import net.minestom.server.coordinate.Point;
|
import net.minestom.server.coordinate.Point;
|
||||||
import net.minestom.server.coordinate.Vec;
|
import net.minestom.server.coordinate.Vec;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import ru.dragonestia.msb3.api.ai.FollowerState;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -14,7 +15,7 @@ public class Path {
|
|||||||
@Getter private final double maxDistance;
|
@Getter private final double maxDistance;
|
||||||
@Getter private final double pathVariance;
|
@Getter private final double pathVariance;
|
||||||
@Getter private final List<PathNode> nodes = new ArrayList<>();
|
@Getter private final List<PathNode> nodes = new ArrayList<>();
|
||||||
private final AtomicReference<PathState> state = new AtomicReference<>(PathState.FOLLOWING);
|
private final AtomicReference<FollowerState> state = new AtomicReference<>(FollowerState.FOLLOWING);
|
||||||
private int index = 0;
|
private int index = 0;
|
||||||
|
|
||||||
public Path(double maxDistance, double pathVariance) {
|
public Path(double maxDistance, double pathVariance) {
|
||||||
@ -22,11 +23,11 @@ public class Path {
|
|||||||
this.pathVariance = pathVariance;
|
this.pathVariance = pathVariance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathState getState() {
|
public FollowerState getState() {
|
||||||
return state.get();
|
return state.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setState(PathState newState) {
|
public void setState(FollowerState newState) {
|
||||||
state.set(newState);
|
state.set(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import net.minestom.server.collision.BoundingBox;
|
|||||||
import net.minestom.server.coordinate.Point;
|
import net.minestom.server.coordinate.Point;
|
||||||
import net.minestom.server.coordinate.Pos;
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.instance.block.Block;
|
import net.minestom.server.instance.block.Block;
|
||||||
|
import ru.dragonestia.msb3.api.ai.FollowerState;
|
||||||
import ru.dragonestia.msb3.api.ai.navigator.node.NodeGenerator;
|
import ru.dragonestia.msb3.api.ai.navigator.node.NodeGenerator;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -50,7 +51,7 @@ public class PathGenerator {
|
|||||||
var closed = new ObjectOpenHashBigSet<PathNode>(maxSize);
|
var closed = new ObjectOpenHashBigSet<PathNode>(maxSize);
|
||||||
|
|
||||||
while (!open.isEmpty() && closed.size64() < maxSize) {
|
while (!open.isEmpty() && closed.size64() < maxSize) {
|
||||||
if (path.getState() == PathState.TERMINATED) {
|
if (path.getState() == FollowerState.TERMINATED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ public class PathGenerator {
|
|||||||
|
|
||||||
if (current == null || !withinDistance(current, target, closeDistance)) {
|
if (current == null || !withinDistance(current, target, closeDistance)) {
|
||||||
if (closestFoundNodes.isEmpty()) {
|
if (closestFoundNodes.isEmpty()) {
|
||||||
path.setState(PathState.DEADLOCKED);
|
path.setState(FollowerState.DEADLOCKED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,25 +101,25 @@ public class PathGenerator {
|
|||||||
Collections.reverse(path.getNodes());
|
Collections.reverse(path.getNodes());
|
||||||
|
|
||||||
if (path.getCurrentType() == PathNode.Type.REPATH) {
|
if (path.getCurrentType() == PathNode.Type.REPATH) {
|
||||||
path.setState(PathState.DEADLOCKED);
|
path.setState(FollowerState.DEADLOCKED);
|
||||||
path.getNodes().clear();
|
path.getNodes().clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.getNodes().isEmpty()) {
|
if (path.getNodes().isEmpty()) {
|
||||||
path.setState(PathState.DEADLOCKED);
|
path.setState(FollowerState.DEADLOCKED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastNode = path.getNodes().getLast();
|
var lastNode = path.getNodes().getLast();
|
||||||
if (getDistanceSquared(lastNode.x(), lastNode.y(), lastNode.z(), target) > (closeDistance * closeDistance)) {
|
if (getDistanceSquared(lastNode.x(), lastNode.y(), lastNode.z(), target) > (closeDistance * closeDistance)) {
|
||||||
path.setState(PathState.DEADLOCKED);
|
path.setState(FollowerState.DEADLOCKED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PathNode pEnd = new PathNode(target, 0, 0, PathNode.Type.WALK, null);
|
PathNode pEnd = new PathNode(target, 0, 0, PathNode.Type.WALK, null);
|
||||||
path.getNodes().add(pEnd);
|
path.getNodes().add(pEnd);
|
||||||
path.setState(PathState.FOLLOWING);
|
path.setState(FollowerState.FOLLOWING);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean withinDistance(PathNode point, Point target, double closeDistance) {
|
private boolean withinDistance(PathNode point, Point target, double closeDistance) {
|
||||||
|
|||||||
@ -1,66 +0,0 @@
|
|||||||
package ru.dragonestia.msb3.api.ai.navigator.follower;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import net.minestom.server.collision.CollisionUtils;
|
|
||||||
import net.minestom.server.coordinate.Point;
|
|
||||||
import net.minestom.server.coordinate.Pos;
|
|
||||||
import net.minestom.server.coordinate.Vec;
|
|
||||||
import net.minestom.server.entity.attribute.Attribute;
|
|
||||||
import net.minestom.server.utils.position.PositionUtils;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import ru.dragonestia.msb3.api.entity.EntityAI;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class GroundNodeFollower implements NodeFollower {
|
|
||||||
|
|
||||||
private final EntityAI entity;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void moveTowards(Point direction, double speed, Point lookAt) {
|
|
||||||
var position = entity.getPosition();
|
|
||||||
var dx = direction.x() - position.x();
|
|
||||||
var dy = direction.y() - position.y();
|
|
||||||
var dz = direction.z() - position.z();
|
|
||||||
|
|
||||||
var dxLook = lookAt.x() - position.x();
|
|
||||||
var dyLook = lookAt.y() - position.y();
|
|
||||||
var dzLook = lookAt.z() - position.z();
|
|
||||||
|
|
||||||
// the purpose of these few lines is to slow down entities when they reach their destination
|
|
||||||
final double distSquared = dx * dx + dy * dy + dz * dz;
|
|
||||||
if (speed > distSquared) {
|
|
||||||
speed = distSquared;
|
|
||||||
}
|
|
||||||
|
|
||||||
var radians = Math.atan2(dz, dx);
|
|
||||||
var speedX = Math.cos(radians) * speed;
|
|
||||||
var speedZ = Math.sin(radians) * speed;
|
|
||||||
var yaw = PositionUtils.getLookYaw(dxLook, dzLook);
|
|
||||||
var pitch = PositionUtils.getLookPitch(dxLook, dyLook, dzLook);
|
|
||||||
var physicsResult = CollisionUtils.handlePhysics(entity, new Vec(speedX, dy > 0? (dy + 0.1) : 0, speedZ));
|
|
||||||
var newPosition = Pos.fromPoint(physicsResult.newPosition());
|
|
||||||
entity.refreshPosition(newPosition.withView(yaw, pitch));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void jump(@Nullable Point point, @Nullable Point target) {
|
|
||||||
if (entity.isOnGround()) {
|
|
||||||
jump(entity.getJumpHeight());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAtPoint(Point point) {
|
|
||||||
var d = entity.getPosition().sub(point);
|
|
||||||
return d.x() * d.x() + d.z() * d.z() < 0.5 * 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void jump(double height) {
|
|
||||||
entity.setVelocity(new Vec(0, height, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double movementSpeed() {
|
|
||||||
return entity.getAttribute(Attribute.MOVEMENT_SPEED).getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
package ru.dragonestia.msb3.api.ai.navigator.follower;
|
|
||||||
|
|
||||||
import net.minestom.server.coordinate.Point;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
public interface NodeFollower {
|
|
||||||
|
|
||||||
void moveTowards(Point target, double speed, Point lookAt);
|
|
||||||
|
|
||||||
void jump(@Nullable Point point, @Nullable Point target);
|
|
||||||
|
|
||||||
boolean isAtPoint(Point point);
|
|
||||||
|
|
||||||
double movementSpeed();
|
|
||||||
}
|
|
||||||
@ -8,7 +8,9 @@ import net.minestom.server.command.builder.CommandContext;
|
|||||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||||
import net.minestom.server.entity.EntityType;
|
import net.minestom.server.entity.EntityType;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import ru.dragonestia.msb3.api.ai.movement.GroundMovementFollower;
|
import ru.dragonestia.msb3.api.ai.movement.target.MovementTarget;
|
||||||
|
import ru.dragonestia.msb3.api.ai.movement.follower.GroundMovementFollower;
|
||||||
|
import ru.dragonestia.msb3.api.debug.DebugMessage;
|
||||||
import ru.dragonestia.msb3.api.entity.EntityAI;
|
import ru.dragonestia.msb3.api.entity.EntityAI;
|
||||||
|
|
||||||
public class PuppeteerSubcommand extends Command {
|
public class PuppeteerSubcommand extends Command {
|
||||||
@ -63,9 +65,9 @@ public class PuppeteerSubcommand extends Command {
|
|||||||
var player = (Player) sender;
|
var player = (Player) sender;
|
||||||
|
|
||||||
sender.sendMessage(Component.text("Set entity path target."));
|
sender.sendMessage(Component.text("Set entity path target."));
|
||||||
entity.getAi().setPathTo(player.getPosition()).thenAccept(result -> {
|
entity.getAi().setMovementTarget(MovementTarget.withPathFinder(entity, player.getPosition(), result -> {
|
||||||
player.sendMessage(Component.text("Entity path target result: " + result, NamedTextColor.YELLOW));
|
player.sendMessage(Component.text("Entity path target result: " + result, NamedTextColor.YELLOW));
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void justComeHere(CommandSender sender, CommandContext ctx) {
|
private void justComeHere(CommandSender sender, CommandContext ctx) {
|
||||||
@ -73,7 +75,9 @@ public class PuppeteerSubcommand extends Command {
|
|||||||
|
|
||||||
var player = (Player) sender;
|
var player = (Player) sender;
|
||||||
sender.sendMessage(Component.text("Set entity path target without PathFinder."));
|
sender.sendMessage(Component.text("Set entity path target without PathFinder."));
|
||||||
entity.getAi().setDestinationPoint(player.getPosition());
|
entity.getAi().setMovementTarget(MovementTarget.justMoveTo(entity, player.getPosition(), result -> {
|
||||||
|
DebugMessage.broadcast("Follower target result: " + result);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void teleport(CommandSender sender, CommandContext ctx) {
|
private void teleport(CommandSender sender, CommandContext ctx) {
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import net.minestom.server.instance.Instance;
|
|||||||
import net.minestom.server.utils.time.TimeUnit;
|
import net.minestom.server.utils.time.TimeUnit;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import ru.dragonestia.msb3.api.ai.AI;
|
import ru.dragonestia.msb3.api.ai.AI;
|
||||||
|
import ru.dragonestia.msb3.api.ai.movement.follower.GroundMovementFollower;
|
||||||
|
import ru.dragonestia.msb3.api.ai.movement.follower.MovementFollower;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -23,6 +25,7 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
public class EntityAI extends LivingEntity {
|
public class EntityAI extends LivingEntity {
|
||||||
|
|
||||||
@Setter private int removalAnimationDelay = 1000;
|
@Setter private int removalAnimationDelay = 1000;
|
||||||
|
private final MovementFollower movementFollower;
|
||||||
private final AI ai = new AI(this);
|
private final AI ai = new AI(this);
|
||||||
|
|
||||||
public EntityAI(@NotNull EntityType entityType) {
|
public EntityAI(@NotNull EntityType entityType) {
|
||||||
@ -31,19 +34,25 @@ public class EntityAI extends LivingEntity {
|
|||||||
|
|
||||||
public EntityAI(@NotNull EntityType entityType, @NotNull UUID uuid) {
|
public EntityAI(@NotNull EntityType entityType, @NotNull UUID uuid) {
|
||||||
super(entityType, uuid);
|
super(entityType, uuid);
|
||||||
|
|
||||||
|
movementFollower = createMovementFollower();
|
||||||
|
|
||||||
getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.2);
|
getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.2);
|
||||||
heal();
|
heal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(long time) {
|
public void update(long time) {
|
||||||
ai.tick(time);
|
if (!isDead()) {
|
||||||
|
ai.tick(time);
|
||||||
|
}
|
||||||
|
|
||||||
super.update(time);
|
super.update(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
|
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
|
||||||
ai.resetDestinationData();
|
if (ai.getMovementTarget() != null) ai.getMovementTarget().cancel();
|
||||||
return super.setInstance(instance, spawnPosition).thenRun(() -> {
|
return super.setInstance(instance, spawnPosition).thenRun(() -> {
|
||||||
var action = ai.getActor().getCurrentAction();
|
var action = ai.getActor().getCurrentAction();
|
||||||
if (action != null) {
|
if (action != null) {
|
||||||
@ -76,7 +85,23 @@ public class EntityAI extends LivingEntity {
|
|||||||
ai.getActor().setActive(false);
|
ai.getActor().setActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить высоту прыжка в блоках
|
||||||
|
* @return Высота прыжка
|
||||||
|
*/
|
||||||
public double getJumpHeight() {
|
public double getJumpHeight() {
|
||||||
return 1.1;
|
return 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить скорость передвижения сущности. Сколько блоков в тик
|
||||||
|
* @return Скорость сущности
|
||||||
|
*/
|
||||||
|
public double getMovementSpeed() {
|
||||||
|
return getAttribute(Attribute.MOVEMENT_SPEED).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MovementFollower createMovementFollower() {
|
||||||
|
return new GroundMovementFollower(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,6 +37,7 @@ public class DebugParamsContext extends PlayerContext {
|
|||||||
if (value) {
|
if (value) {
|
||||||
if (taskDebugRendererAiPathFinder != null) return;
|
if (taskDebugRendererAiPathFinder != null) return;
|
||||||
|
|
||||||
|
/* TODO: fix it
|
||||||
taskDebugRendererAiPathFinder = Scheduler.ofPlayer(getPlayer()).repeatingTask(() -> {
|
taskDebugRendererAiPathFinder = Scheduler.ofPlayer(getPlayer()).repeatingTask(() -> {
|
||||||
for (var entity: getPlayer().getInstance().getNearbyEntities(getPlayer().getPosition(), 32)) {
|
for (var entity: getPlayer().getInstance().getNearbyEntities(getPlayer().getPosition(), 32)) {
|
||||||
if (entity instanceof EntityAI creature) {
|
if (entity instanceof EntityAI creature) {
|
||||||
@ -49,6 +50,7 @@ public class DebugParamsContext extends PlayerContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, Duration.ofMillis(500));
|
}, Duration.ofMillis(500));
|
||||||
|
*/
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user