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.ai.NPC;
|
||||||
import com.bartlomiejpluta.base.api.entity.Entity;
|
import com.bartlomiejpluta.base.api.entity.Entity;
|
||||||
import com.bartlomiejpluta.base.api.map.layer.object.ObjectLayer;
|
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 com.bartlomiejpluta.base.util.pathfinder.PathFinder;
|
||||||
import org.joml.Vector2i;
|
import lombok.NonNull;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
public class FollowEntityAI implements AI {
|
public abstract class FollowEntityAI<N extends NPC, T extends Entity> implements AI {
|
||||||
private final NPC npc;
|
|
||||||
private final Entity target;
|
|
||||||
private final PathFinder pathFinder;
|
|
||||||
private final int range;
|
|
||||||
|
|
||||||
private final float recalculateInterval;
|
private final PathFinder finder;
|
||||||
private float accumulator = 0.0f;
|
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) {
|
@Setter
|
||||||
this.pathFinder = pathFinder;
|
private AI idleAI;
|
||||||
this.recalculateInterval = recalculateInterval;
|
|
||||||
|
@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.npc = npc;
|
||||||
this.range = range;
|
|
||||||
this.target = target;
|
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
|
@Override
|
||||||
public void nextActivity(ObjectLayer layer, float dt) {
|
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) {
|
if (distance == 1) {
|
||||||
var path = pathFinder.findSequence(layer, npc.getCoordinates(), target.getCoordinates());
|
npc.setFaceDirection(npc.getDirectionTowards(target));
|
||||||
|
interact(npc, target);
|
||||||
|
} else if (distance < range) {
|
||||||
|
follow(npc, target);
|
||||||
|
|
||||||
if (!path.isEmpty()) {
|
if (path == null) {
|
||||||
accumulator = recalculateInterval;
|
path = finder.findPath(layer, npc, target.getCoordinates());
|
||||||
|
executor.setPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
var node = path.getFirst().sub(npc.getCoordinates(), new Vector2i());
|
executor.execute(layer, dt);
|
||||||
var direction = Direction.ofVector(node);
|
|
||||||
var movement = npc.prepareMovement(direction);
|
|
||||||
layer.pushMovement(movement);
|
|
||||||
} else {
|
} 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,48 @@
|
|||||||
package com.bartlomiejpluta.base.util.path;
|
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 com.bartlomiejpluta.base.api.move.Direction;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class NPCPath<T extends NPC> implements Path<T> {
|
public class EntityPath<T extends Entity> implements Path<T> {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final List<PathSegment<T>> path = new ArrayList<>();
|
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);
|
path.add(segment);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NPCPath<T> addFirst(PathSegment<T> segment) {
|
public EntityPath<T> addFirst(PathSegment<T> segment) {
|
||||||
path.add(0, segment);
|
path.add(0, segment);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NPCPath<T> move(Direction direction) {
|
public EntityPath<T> move(Direction direction) {
|
||||||
path.add(new MoveSegment<>(direction));
|
path.add(new MoveSegment<>(direction));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NPCPath<T> move(Direction direction, boolean ignore) {
|
public EntityPath<T> move(Direction direction, boolean ignore) {
|
||||||
path.add(new MoveSegment<>(direction, ignore));
|
path.add(new MoveSegment<>(direction, ignore));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NPCPath<T> turn(Direction direction) {
|
public EntityPath<T> turn(Direction direction) {
|
||||||
path.add(new TurnSegment<>(direction));
|
path.add(new TurnSegment<>(direction));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NPCPath<T> wait(float seconds) {
|
public EntityPath<T> wait(float seconds) {
|
||||||
path.add(new WaitSegment<>(seconds));
|
path.add(new WaitSegment<>(seconds));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NPCPath<T> run(Runnable runnable) {
|
public EntityPath<T> run(Runnable runnable) {
|
||||||
path.add(new RunSegment<>(runnable));
|
path.add(new RunSegment<>(runnable));
|
||||||
return this;
|
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.Direction;
|
||||||
import com.bartlomiejpluta.base.api.move.Movable;
|
import com.bartlomiejpluta.base.api.move.Movable;
|
||||||
|
import org.joml.Vector2ic;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -24,6 +25,28 @@ public class MovementPath<T extends Movable> implements Path<T> {
|
|||||||
return this;
|
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) {
|
public boolean contains(int x, int y) {
|
||||||
for (var segment : path) {
|
for (var segment : path) {
|
||||||
if (segment.x == x && segment.y == y) {
|
if (segment.x == x && segment.y == y) {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.bartlomiejpluta.base.util.path;
|
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.map.layer.object.ObjectLayer;
|
||||||
import com.bartlomiejpluta.base.api.move.Direction;
|
import com.bartlomiejpluta.base.api.move.Direction;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
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;
|
private final Direction direction;
|
||||||
|
|
||||||
public TurnSegment(Direction direction) {
|
public TurnSegment(Direction direction) {
|
||||||
@@ -14,8 +14,8 @@ public class TurnSegment<T extends NPC> implements PathSegment<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PathProgress perform(T npc, ObjectLayer layer, float dt) {
|
public PathProgress perform(T entity, ObjectLayer layer, float dt) {
|
||||||
npc.setFaceDirection(direction);
|
entity.setFaceDirection(direction);
|
||||||
return PathProgress.SEGMENT_DONE;
|
return PathProgress.SEGMENT_DONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user