Extract PathExecutor from FollowPathAI strategy
This commit is contained in:
@@ -1,142 +1,52 @@
|
|||||||
package com.bartlomiejpluta.base.api.game.ai;
|
package com.bartlomiejpluta.base.api.game.ai;
|
||||||
|
|
||||||
import com.bartlomiejpluta.base.api.game.entity.Direction;
|
import com.bartlomiejpluta.base.api.game.entity.Direction;
|
||||||
import com.bartlomiejpluta.base.api.game.entity.Movement;
|
|
||||||
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
|
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
|
||||||
|
import com.bartlomiejpluta.base.api.util.path.NPCPath;
|
||||||
import java.util.LinkedList;
|
import com.bartlomiejpluta.base.api.util.path.PathExecutor;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class FollowPathAI implements AI {
|
public class FollowPathAI implements AI {
|
||||||
private final List<PathSegment> path = new LinkedList<>();
|
private final PathExecutor<NPC> executor;
|
||||||
private final NPC npc;
|
private final NPCPath path;
|
||||||
private final boolean repeat;
|
|
||||||
|
|
||||||
private int current = 0;
|
|
||||||
|
|
||||||
public FollowPathAI(NPC npc) {
|
public FollowPathAI(NPC npc) {
|
||||||
this(npc, true);
|
this(npc, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FollowPathAI(NPC npc, boolean repeat) {
|
public FollowPathAI(NPC npc, boolean repeat) {
|
||||||
this.npc = npc;
|
var path = new NPCPath();
|
||||||
this.repeat = repeat;
|
this.executor = new PathExecutor<>(npc, repeat, path);
|
||||||
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void nextActivity(ObjectLayer layer, float dt) {
|
public void nextActivity(ObjectLayer layer, float dt) {
|
||||||
if (!repeat && isRetired()) {
|
executor.execute(layer, dt);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!npc.isMoving()) {
|
|
||||||
PathSegment item = (PathSegment) path.get(current % path.size());
|
|
||||||
if (item.perform(npc, layer, dt)) {
|
|
||||||
++current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRetired() {
|
|
||||||
return current == path.size() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FollowPathAI move(Direction direction) {
|
public FollowPathAI move(Direction direction) {
|
||||||
return move(direction, false);
|
path.move(direction);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FollowPathAI move(Direction direction, boolean ignore) {
|
public FollowPathAI move(Direction direction, boolean ignore) {
|
||||||
path.add(new Move(direction, ignore));
|
path.move(direction, ignore);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FollowPathAI turn(Direction direction) {
|
public FollowPathAI turn(Direction direction) {
|
||||||
path.add(new Turn(direction));
|
path.turn(direction);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FollowPathAI wait(float seconds) {
|
public FollowPathAI wait(float seconds) {
|
||||||
path.add(new Wait(seconds));
|
path.wait(seconds);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FollowPathAI run(Runnable runnable) {
|
public FollowPathAI run(Runnable runnable) {
|
||||||
path.add(new Run(runnable));
|
path.run(runnable);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface PathSegment {
|
|
||||||
boolean perform(NPC npc, ObjectLayer layer, float dt);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Move implements PathSegment {
|
|
||||||
private final Direction direction;
|
|
||||||
private final boolean ignore;
|
|
||||||
|
|
||||||
public Move(Direction direction, boolean ignore) {
|
|
||||||
this.direction = direction;
|
|
||||||
this.ignore = ignore;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean perform(NPC npc, ObjectLayer layer, float dt) {
|
|
||||||
Movement movement = npc.prepareMovement(direction);
|
|
||||||
|
|
||||||
if (ignore || layer.isTileReachable(movement.getTo())) {
|
|
||||||
layer.pushMovement(movement);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Turn implements PathSegment {
|
|
||||||
private final Direction direction;
|
|
||||||
|
|
||||||
public Turn(Direction direction) {
|
|
||||||
this.direction = direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean perform(NPC npc, ObjectLayer layer, float dt) {
|
|
||||||
npc.setFaceDirection(direction);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Wait implements PathSegment {
|
|
||||||
private float accumulator = 0.0f;
|
|
||||||
private final float seconds;
|
|
||||||
|
|
||||||
public Wait(float seconds) {
|
|
||||||
this.seconds = seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean perform(NPC npc, ObjectLayer layer, float dt) {
|
|
||||||
accumulator += dt;
|
|
||||||
|
|
||||||
if (accumulator > seconds) {
|
|
||||||
accumulator = 0.0f;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Run implements PathSegment {
|
|
||||||
private final Runnable runnable;
|
|
||||||
|
|
||||||
private Run(Runnable runnable) {
|
|
||||||
this.runnable = runnable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean perform(NPC npc, ObjectLayer layer, float dt) {
|
|
||||||
runnable.run();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import com.bartlomiejpluta.base.api.internal.object.Placeable;
|
|||||||
import com.bartlomiejpluta.base.api.internal.render.Renderable;
|
import com.bartlomiejpluta.base.api.internal.render.Renderable;
|
||||||
import org.joml.Vector2ic;
|
import org.joml.Vector2ic;
|
||||||
|
|
||||||
public interface Entity extends Placeable, Renderable, Updatable {
|
public interface Entity extends Placeable, Movable, Renderable, Updatable {
|
||||||
void setStepSize(float x, float y);
|
void setStepSize(float x, float y);
|
||||||
|
|
||||||
Vector2ic getCoordinates();
|
Vector2ic getCoordinates();
|
||||||
@@ -15,10 +15,6 @@ public interface Entity extends Placeable, Renderable, Updatable {
|
|||||||
|
|
||||||
void setCoordinates(int x, int y);
|
void setCoordinates(int x, int y);
|
||||||
|
|
||||||
Movement prepareMovement(Direction direction);
|
|
||||||
|
|
||||||
Movement getMovement();
|
|
||||||
|
|
||||||
Direction getFaceDirection();
|
Direction getFaceDirection();
|
||||||
|
|
||||||
void setFaceDirection(Direction direction);
|
void setFaceDirection(Direction direction);
|
||||||
@@ -27,8 +23,6 @@ public interface Entity extends Placeable, Renderable, Updatable {
|
|||||||
|
|
||||||
void setAnimationSpeed(float speed);
|
void setAnimationSpeed(float speed);
|
||||||
|
|
||||||
boolean isMoving();
|
|
||||||
|
|
||||||
int chebyshevDistance(Entity other);
|
int chebyshevDistance(Entity other);
|
||||||
|
|
||||||
int manhattanDistance(Entity other);
|
int manhattanDistance(Entity other);
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.bartlomiejpluta.base.api.game.entity;
|
||||||
|
|
||||||
|
public interface Movable {
|
||||||
|
Movement prepareMovement(Direction direction);
|
||||||
|
|
||||||
|
Movement getMovement();
|
||||||
|
|
||||||
|
boolean isMoving();
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.bartlomiejpluta.base.api.util.path;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.api.game.entity.Direction;
|
||||||
|
import com.bartlomiejpluta.base.api.game.entity.Movable;
|
||||||
|
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class MoveSegment<T extends Movable> implements PathSegment<T> {
|
||||||
|
private final Direction direction;
|
||||||
|
private final boolean ignore;
|
||||||
|
|
||||||
|
public MoveSegment(Direction direction) {
|
||||||
|
this(direction, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoveSegment(Direction direction, boolean ignore) {
|
||||||
|
this.direction = Objects.requireNonNull(direction);
|
||||||
|
this.ignore = ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean perform(T movable, ObjectLayer layer, float dt) {
|
||||||
|
var movement = movable.prepareMovement(direction);
|
||||||
|
|
||||||
|
if (ignore || layer.isTileReachable(movement.getTo())) {
|
||||||
|
layer.pushMovement(movement);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.bartlomiejpluta.base.api.util.path;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.api.game.ai.NPC;
|
||||||
|
import com.bartlomiejpluta.base.api.game.entity.Direction;
|
||||||
|
|
||||||
|
public class NPCPath extends Path<NPC> {
|
||||||
|
|
||||||
|
public NPCPath turn(Direction direction) {
|
||||||
|
path.add(new TurnSegment<>(direction));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.bartlomiejpluta.base.api.util.path;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.api.game.entity.Direction;
|
||||||
|
import com.bartlomiejpluta.base.api.game.entity.Movable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Path<T extends Movable> {
|
||||||
|
protected final List<PathSegment<T>> path = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<PathSegment<T>> getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path<T> add(PathSegment<T> segment) {
|
||||||
|
path.add(segment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path<T> move(Direction direction) {
|
||||||
|
path.add(new MoveSegment<>(direction));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path<T> move(Direction direction, boolean ignore) {
|
||||||
|
path.add(new MoveSegment<>(direction, ignore));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path<T> wait(float seconds) {
|
||||||
|
path.add(new WaitSegment<>(seconds));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path<T> run(Runnable runnable) {
|
||||||
|
path.add(new RunSegment<>(runnable));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.bartlomiejpluta.base.api.util.path;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.api.game.entity.Movable;
|
||||||
|
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class PathExecutor<T extends Movable> {
|
||||||
|
protected final List<PathSegment<T>> path;
|
||||||
|
private final T movable;
|
||||||
|
private final boolean repeat;
|
||||||
|
|
||||||
|
private int current = 0;
|
||||||
|
|
||||||
|
public PathExecutor(T movable, boolean repeat, Path<T> path) {
|
||||||
|
this.movable = movable;
|
||||||
|
this.repeat = repeat;
|
||||||
|
this.path = Objects.requireNonNull(path).getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean execute(ObjectLayer layer, float dt) {
|
||||||
|
if (!repeat && isRetired()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!movable.isMoving()) {
|
||||||
|
var item = path.get(current % path.size());
|
||||||
|
if (item.perform(movable, layer, dt)) {
|
||||||
|
++current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRetired() {
|
||||||
|
return current == path.size() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.bartlomiejpluta.base.api.util.path;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.api.game.entity.Movable;
|
||||||
|
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
|
||||||
|
|
||||||
|
public interface PathSegment<T extends Movable> {
|
||||||
|
boolean perform(T movable, ObjectLayer layer, float dt);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.bartlomiejpluta.base.api.util.path;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.api.game.entity.Movable;
|
||||||
|
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
public class RunSegment<T extends Movable> implements PathSegment<T> {
|
||||||
|
private final Runnable runnable;
|
||||||
|
|
||||||
|
public RunSegment(Runnable runnable) {
|
||||||
|
this.runnable = requireNonNull(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean perform(T movable, ObjectLayer layer, float dt) {
|
||||||
|
runnable.run();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.bartlomiejpluta.base.api.util.path;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.api.game.ai.NPC;
|
||||||
|
import com.bartlomiejpluta.base.api.game.entity.Direction;
|
||||||
|
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
public class TurnSegment<T extends NPC> implements PathSegment<T> {
|
||||||
|
private final Direction direction;
|
||||||
|
|
||||||
|
public TurnSegment(Direction direction) {
|
||||||
|
this.direction = requireNonNull(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean perform(T npc, ObjectLayer layer, float dt) {
|
||||||
|
npc.setFaceDirection(direction);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.bartlomiejpluta.base.api.util.path;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.api.game.entity.Movable;
|
||||||
|
import com.bartlomiejpluta.base.api.game.map.layer.object.ObjectLayer;
|
||||||
|
|
||||||
|
public class WaitSegment<T extends Movable> implements PathSegment<T> {
|
||||||
|
private final float seconds;
|
||||||
|
private float accumulator = 0.0f;
|
||||||
|
|
||||||
|
public WaitSegment(float seconds) {
|
||||||
|
this.seconds = seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean perform(T movable, ObjectLayer layer, float dt) {
|
||||||
|
accumulator += dt;
|
||||||
|
|
||||||
|
if (accumulator > seconds) {
|
||||||
|
accumulator = 0.0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user