Improve FollowEntityAI to recompute path only on demand

This commit is contained in:
2021-04-07 09:25:40 +02:00
parent 125d063af3
commit 8d709709a8
4 changed files with 94 additions and 39 deletions

View File

@@ -4,47 +4,79 @@ import com.bartlomiejpluta.base.api.ai.AI;
import com.bartlomiejpluta.base.api.ai.NPC;
import com.bartlomiejpluta.base.api.entity.Entity;
import com.bartlomiejpluta.base.api.map.layer.object.ObjectLayer;
import com.bartlomiejpluta.base.api.move.Direction;
import com.bartlomiejpluta.base.api.move.MoveEvent;
import com.bartlomiejpluta.base.util.path.MovementPath;
import com.bartlomiejpluta.base.util.path.PathExecutor;
import com.bartlomiejpluta.base.util.pathfinder.PathFinder;
import org.joml.Vector2i;
import lombok.NonNull;
import lombok.Setter;
public class FollowEntityAI implements AI {
private final NPC npc;
private final Entity target;
private final PathFinder pathFinder;
private final int range;
public abstract class FollowEntityAI<N extends NPC, T extends Entity> implements AI {
private final float recalculateInterval;
private float accumulator = 0.0f;
private final PathFinder finder;
private final PathExecutor<N> executor;
private final N npc;
private final T target;
public FollowEntityAI(PathFinder pathFinder, float recalculateInterval, NPC npc, Entity target, int range) {
this.pathFinder = pathFinder;
this.recalculateInterval = recalculateInterval;
@Setter
private AI idleAI;
@Setter
private int range = 20;
private MovementPath<N> path = null;
protected FollowEntityAI(@NonNull PathFinder finder, @NonNull N npc, @NonNull T target) {
this.finder = finder;
this.npc = npc;
this.range = range;
this.target = target;
this.executor = new PathExecutor<>(npc);
}
public void recomputePath() {
path = null;
}
public void recomputePath(@NonNull MoveEvent event) {
var movable = event.getMovable();
// Refresh only when target has been displaced
// or another entity is blocking current path
if (movable == target || (path != null && path.contains(movable))) {
path = null;
}
}
@Override
public void nextActivity(ObjectLayer layer, float dt) {
var distance = npc.manhattanDistance(target);
if (!npc.isMoving()) {
var distance = npc.manhattanDistance(target);
if (!npc.isMoving() && 1 < distance && distance < range && accumulator >= recalculateInterval) {
var path = pathFinder.findSequence(layer, npc.getCoordinates(), target.getCoordinates());
if (distance == 1) {
npc.setFaceDirection(npc.getDirectionTowards(target));
interact(npc, target);
} else if (distance < range) {
follow(npc, target);
if (!path.isEmpty()) {
accumulator = recalculateInterval;
if (path == null) {
path = finder.findPath(layer, npc, target.getCoordinates());
executor.setPath(path);
}
var node = path.getFirst().sub(npc.getCoordinates(), new Vector2i());
var direction = Direction.ofVector(node);
var movement = npc.prepareMovement(direction);
layer.pushMovement(movement);
executor.execute(layer, dt);
} else {
accumulator = 0.0f;
idle(npc, target);
if (idleAI != null) {
idleAI.nextActivity(layer, dt);
}
}
}
accumulator += dt;
}
protected abstract void interact(N npc, T target);
protected abstract void follow(N npc, T target);
protected abstract void idle(N npc, T target);
}

View File

@@ -1,48 +1,48 @@
package com.bartlomiejpluta.base.util.path;
import com.bartlomiejpluta.base.api.ai.NPC;
import com.bartlomiejpluta.base.api.entity.Entity;
import com.bartlomiejpluta.base.api.move.Direction;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
public class NPCPath<T extends NPC> implements Path<T> {
public class EntityPath<T extends Entity> implements Path<T> {
@Getter
private final List<PathSegment<T>> path = new ArrayList<>();
public NPCPath<T> add(PathSegment<T> segment) {
public EntityPath<T> add(PathSegment<T> segment) {
path.add(segment);
return this;
}
public NPCPath<T> addFirst(PathSegment<T> segment) {
public EntityPath<T> addFirst(PathSegment<T> segment) {
path.add(0, segment);
return this;
}
public NPCPath<T> move(Direction direction) {
public EntityPath<T> move(Direction direction) {
path.add(new MoveSegment<>(direction));
return this;
}
public NPCPath<T> move(Direction direction, boolean ignore) {
public EntityPath<T> move(Direction direction, boolean ignore) {
path.add(new MoveSegment<>(direction, ignore));
return this;
}
public NPCPath<T> turn(Direction direction) {
public EntityPath<T> turn(Direction direction) {
path.add(new TurnSegment<>(direction));
return this;
}
public NPCPath<T> wait(float seconds) {
public EntityPath<T> wait(float seconds) {
path.add(new WaitSegment<>(seconds));
return this;
}
public NPCPath<T> run(Runnable runnable) {
public EntityPath<T> run(Runnable runnable) {
path.add(new RunSegment<>(runnable));
return this;
}

View File

@@ -2,6 +2,7 @@ package com.bartlomiejpluta.base.util.path;
import com.bartlomiejpluta.base.api.move.Direction;
import com.bartlomiejpluta.base.api.move.Movable;
import org.joml.Vector2ic;
import java.util.ArrayList;
import java.util.List;
@@ -24,6 +25,28 @@ public class MovementPath<T extends Movable> implements Path<T> {
return this;
}
public boolean contains(Movable movable) {
var coordinates = movable.getCoordinates();
for (var segment : path) {
if (segment.x == coordinates.x() && segment.y == coordinates.y()) {
return true;
}
}
return false;
}
public boolean contains(Vector2ic vector) {
for (var segment : path) {
if (segment.x == vector.x() && segment.y == vector.y()) {
return true;
}
}
return false;
}
public boolean contains(int x, int y) {
for (var segment : path) {
if (segment.x == x && segment.y == y) {

View File

@@ -1,12 +1,12 @@
package com.bartlomiejpluta.base.util.path;
import com.bartlomiejpluta.base.api.ai.NPC;
import com.bartlomiejpluta.base.api.entity.Entity;
import com.bartlomiejpluta.base.api.map.layer.object.ObjectLayer;
import com.bartlomiejpluta.base.api.move.Direction;
import static java.util.Objects.requireNonNull;
public class TurnSegment<T extends NPC> implements PathSegment<T> {
public class TurnSegment<T extends Entity> implements PathSegment<T> {
private final Direction direction;
public TurnSegment(Direction direction) {
@@ -14,8 +14,8 @@ public class TurnSegment<T extends NPC> implements PathSegment<T> {
}
@Override
public PathProgress perform(T npc, ObjectLayer layer, float dt) {
npc.setFaceDirection(direction);
public PathProgress perform(T entity, ObjectLayer layer, float dt) {
entity.setFaceDirection(direction);
return PathProgress.SEGMENT_DONE;
}
}