refactor: started refactoring NodeFollower
This commit is contained in:
parent
0e5a82d02f
commit
c0a6b80742
@ -2,20 +2,26 @@ package ru.dragonestia.msb3.api.ai;
|
|||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
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.coordinate.Vec;
|
import net.minestom.server.coordinate.Vec;
|
||||||
|
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.*;
|
||||||
import ru.dragonestia.msb3.api.ai.navigator.follower.GroundNodeFollower;
|
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.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 ru.dragonestia.msb3.api.util.UncheckedRunnable;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
@Getter
|
@Getter
|
||||||
public class AI {
|
public class AI {
|
||||||
|
|
||||||
@ -23,11 +29,14 @@ public class AI {
|
|||||||
private DestinationPoint destinationData;
|
private DestinationPoint destinationData;
|
||||||
@Setter private NodeGenerator nodeGenerator = new GroundNodeGenerator();
|
@Setter private NodeGenerator nodeGenerator = new GroundNodeGenerator();
|
||||||
@Setter private NodeFollower nodeFollower;
|
@Setter private NodeFollower nodeFollower;
|
||||||
|
private final MovementFollower movementFollower;
|
||||||
@Getter private final Actor actor;
|
@Getter private final Actor actor;
|
||||||
|
@Setter private Point destinationPoint;
|
||||||
|
|
||||||
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);
|
nodeFollower = new GroundNodeFollower(entity);
|
||||||
|
movementFollower = new GroundMovementFollower(entity);
|
||||||
actor = new Actor(entity);
|
actor = new Actor(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,9 +117,34 @@ public class AI {
|
|||||||
if (action != null) action.tick(actor, entity, delta);
|
if (action != null) action.tick(actor, entity, delta);
|
||||||
|
|
||||||
if (destinationData != null) tickNavigator();
|
if (destinationData != null) tickNavigator();
|
||||||
|
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() {
|
private void tickNavigator() {
|
||||||
if (entity.getPosition().distance(destinationData.position()) < destinationData.arrivalDistance()) {
|
if (entity.getPosition().distance(destinationData.position()) < destinationData.arrivalDistance()) {
|
||||||
var future = destinationData.future();
|
var future = destinationData.future();
|
||||||
@ -123,7 +157,7 @@ public class AI {
|
|||||||
var nextTarget = path.getNext();
|
var nextTarget = path.getNext();
|
||||||
|
|
||||||
if (currentTarget == null || path.getCurrentType() == PathNode.Type.REPATH || path.getCurrentType() == null) {
|
if (currentTarget == null || path.getCurrentType() == PathNode.Type.REPATH || path.getCurrentType() == null) {
|
||||||
recalculatePath(path);
|
recalculatePath();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +176,10 @@ public class AI {
|
|||||||
checkFinishedPath(path, destinationData.future());
|
checkFinishedPath(path, destinationData.future());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recalculatePath(Path originalPath) {
|
public void recalculatePath() {
|
||||||
|
if (destinationData == null) return;
|
||||||
|
|
||||||
|
var originalPath = destinationData.path();
|
||||||
var newPath = PathGenerator.generate(entity.getInstance(),
|
var newPath = PathGenerator.generate(entity.getInstance(),
|
||||||
entity.getPosition(),
|
entity.getPosition(),
|
||||||
destinationData.position(),
|
destinationData.position(),
|
||||||
@ -173,4 +210,8 @@ public class AI {
|
|||||||
public boolean isCompleted() {
|
public boolean isCompleted() {
|
||||||
return getCurrentPathState().isCompleted();
|
return getCurrentPathState().isCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getMovementSpeed() {
|
||||||
|
return entity.getAttribute(Attribute.MOVEMENT_SPEED).getValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
package ru.dragonestia.msb3.api.ai.movement;
|
||||||
|
|
||||||
|
import net.minestom.server.collision.CollisionUtils;
|
||||||
|
import net.minestom.server.collision.PhysicsResult;
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
|
import ru.dragonestia.msb3.api.entity.EntityAI;
|
||||||
|
|
||||||
|
public class GroundMovementFollower extends MovementFollower {
|
||||||
|
|
||||||
|
private PhysicsResult lastPhysicResult;
|
||||||
|
|
||||||
|
public GroundMovementFollower(EntityAI entity) {
|
||||||
|
super(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double squaredDistance(Point destination) {
|
||||||
|
return getEntity().getPosition().withY(0).distanceSquared(destination.withY(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PhysicsResult moveResult(Point direction, double speed) {
|
||||||
|
var radians = Math.atan2(direction.z(), direction.x());
|
||||||
|
var speedX = Math.cos(radians) * speed;
|
||||||
|
var speedZ = Math.sin(radians) * speed;
|
||||||
|
|
||||||
|
double speedY = 0;
|
||||||
|
if (lastPhysicResult != null) {
|
||||||
|
double highestDeltaCollision = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < lastPhysicResult.collisionShapes().length; i++) {
|
||||||
|
var shape = lastPhysicResult.collisionShapes()[i];
|
||||||
|
var shapePos = lastPhysicResult.collisionShapePositions()[i];
|
||||||
|
if (shape == null || shapePos == null) continue;
|
||||||
|
|
||||||
|
var point = shapePos.add(shape.relativeEnd());
|
||||||
|
var deltaH = point.y() - getEntity().getPosition().y();
|
||||||
|
if (deltaH > getMaxHeightStep() && deltaH > getJumpHeight()) continue;
|
||||||
|
highestDeltaCollision = Math.max(highestDeltaCollision, deltaH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (highestDeltaCollision > 0 && getEntity().isOnGround()) {
|
||||||
|
speedY = highestDeltaCollision + 0.08;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastPhysicResult = CollisionUtils.handlePhysics(getEntity(), new Vec(speedX, speedY, speedZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PhysicsResult move(Point direction, double speed) {
|
||||||
|
return super.move(direction, speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PhysicsResult move(Point direction, double speed, Point lookDirection) {
|
||||||
|
return super.move(direction, speed, lookDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getJumpHeight() {
|
||||||
|
return 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMaxHeightStep() {
|
||||||
|
return 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void jump() {
|
||||||
|
jump(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void jump(Point direction) {
|
||||||
|
jump(direction.x(), direction.y(), direction.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void jump(double x, double z) {
|
||||||
|
jump(x, getEntity().getJumpHeight(), z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void jump(double x, double y, double z) {
|
||||||
|
if (!getEntity().isOnGround()) return;
|
||||||
|
getEntity().setVelocity(new Vec(x, y, z).mul(7.1));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package ru.dragonestia.msb3.api.ai.movement;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import net.minestom.server.collision.PhysicsResult;
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.coordinate.Pos;
|
||||||
|
import net.minestom.server.utils.position.PositionUtils;
|
||||||
|
import ru.dragonestia.msb3.api.entity.EntityAI;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class MovementFollower {
|
||||||
|
|
||||||
|
private final EntityAI entity;
|
||||||
|
|
||||||
|
public double squaredDistance(Point destination) {
|
||||||
|
return entity.getDistanceSquared(destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract PhysicsResult moveResult(Point direction, double speed);
|
||||||
|
|
||||||
|
public PhysicsResult move(Point direction, double speed) {
|
||||||
|
var physicResult = moveResult(direction, speed);
|
||||||
|
var newPosition = entity.getPosition().withCoord(physicResult.newPosition());
|
||||||
|
entity.refreshPosition(newPosition);
|
||||||
|
return physicResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhysicsResult move(Point direction, double speed, Point lookDirection) {
|
||||||
|
var yaw = PositionUtils.getLookYaw(lookDirection.x(), direction.z());
|
||||||
|
var pitch = PositionUtils.getLookPitch(lookDirection.x(), lookDirection.y(), direction.z());
|
||||||
|
var physicResult = moveResult(direction, speed);
|
||||||
|
var newPosition = Pos.fromPoint(physicResult.newPosition()).withView(yaw, pitch);
|
||||||
|
entity.refreshPosition(newPosition);
|
||||||
|
return physicResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhysicsResult moveTo(Point destination, double speed, Point lookAt) {
|
||||||
|
var direction = destination.sub(entity.getPosition());
|
||||||
|
var lookDirection = lookAt.sub(entity.getPosition());
|
||||||
|
return move(direction, speed, lookDirection);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ 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.entity.EntityAI;
|
import ru.dragonestia.msb3.api.entity.EntityAI;
|
||||||
|
|
||||||
public class PuppeteerSubcommand extends Command {
|
public class PuppeteerSubcommand extends Command {
|
||||||
@ -21,7 +22,9 @@ public class PuppeteerSubcommand extends Command {
|
|||||||
addSyntax(this::createEntity, ArgumentType.Literal("create"));
|
addSyntax(this::createEntity, ArgumentType.Literal("create"));
|
||||||
addSyntax(this::removeEntity, ArgumentType.Literal("remove"));
|
addSyntax(this::removeEntity, ArgumentType.Literal("remove"));
|
||||||
addSyntax(this::comeHere, ArgumentType.Literal("come_here"));
|
addSyntax(this::comeHere, ArgumentType.Literal("come_here"));
|
||||||
|
addSyntax(this::justComeHere, ArgumentType.Literal("just_come_here"));
|
||||||
addSyntax(this::teleport, ArgumentType.Literal("teleport"));
|
addSyntax(this::teleport, ArgumentType.Literal("teleport"));
|
||||||
|
addSyntax(this::jump, ArgumentType.Literal("jump"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void defaultExecutor(CommandSender sender, CommandContext ctx) {
|
private void defaultExecutor(CommandSender sender, CommandContext ctx) {
|
||||||
@ -65,6 +68,14 @@ public class PuppeteerSubcommand extends Command {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void justComeHere(CommandSender sender, CommandContext ctx) {
|
||||||
|
if (isNotCreated(sender)) return;
|
||||||
|
|
||||||
|
var player = (Player) sender;
|
||||||
|
sender.sendMessage(Component.text("Set entity path target without PathFinder."));
|
||||||
|
entity.getAi().setDestinationPoint(player.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
private void teleport(CommandSender sender, CommandContext ctx) {
|
private void teleport(CommandSender sender, CommandContext ctx) {
|
||||||
if (isNotCreated(sender)) return;
|
if (isNotCreated(sender)) return;
|
||||||
|
|
||||||
@ -74,6 +85,15 @@ public class PuppeteerSubcommand extends Command {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void jump(CommandSender sender, CommandContext ctx) {
|
||||||
|
if (isNotCreated(sender)) return;
|
||||||
|
|
||||||
|
if (entity.getAi().getMovementFollower() instanceof GroundMovementFollower follower) {
|
||||||
|
follower.jump();
|
||||||
|
sender.sendMessage(Component.text("Jumped.", NamedTextColor.YELLOW));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isNotCreated(CommandSender sender) {
|
private boolean isNotCreated(CommandSender sender) {
|
||||||
boolean failed = false;
|
boolean failed = false;
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
|
|||||||
@ -77,6 +77,6 @@ public class EntityAI extends LivingEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public double getJumpHeight() {
|
public double getJumpHeight() {
|
||||||
return 2.1;
|
return 1.1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user