Improve FollowEntityAI to recompute path only on demand
This commit is contained in:
@@ -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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
protected abstract void interact(N npc, T target);
|
||||
|
||||
accumulator += dt;
|
||||
}
|
||||
protected abstract void follow(N npc, T target);
|
||||
|
||||
protected abstract void idle(N npc, T target);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user