From 5c2e33eedcd43645577806e989a8bef9914960c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Przemys=C5=82aw=20Pluta?= Date: Sat, 20 Mar 2021 22:53:41 +0100 Subject: [PATCH] Extract PathExecutor from FollowPathAI strategy --- .../base/api/game/ai/FollowPathAI.java | 118 +++--------------- .../base/api/game/entity/Entity.java | 8 +- .../base/api/game/entity/Movable.java | 9 ++ .../base/api/util/path/MoveSegment.java | 33 +++++ .../base/api/util/path/NPCPath.java | 12 ++ .../base/api/util/path/Path.java | 40 ++++++ .../base/api/util/path/PathExecutor.java | 40 ++++++ .../base/api/util/path/PathSegment.java | 8 ++ .../base/api/util/path/RunSegment.java | 20 +++ .../base/api/util/path/TurnSegment.java | 21 ++++ .../base/api/util/path/WaitSegment.java | 25 ++++ 11 files changed, 223 insertions(+), 111 deletions(-) create mode 100644 api/src/main/java/com/bartlomiejpluta/base/api/game/entity/Movable.java create mode 100644 api/src/main/java/com/bartlomiejpluta/base/api/util/path/MoveSegment.java create mode 100644 api/src/main/java/com/bartlomiejpluta/base/api/util/path/NPCPath.java create mode 100644 api/src/main/java/com/bartlomiejpluta/base/api/util/path/Path.java create mode 100644 api/src/main/java/com/bartlomiejpluta/base/api/util/path/PathExecutor.java create mode 100644 api/src/main/java/com/bartlomiejpluta/base/api/util/path/PathSegment.java create mode 100644 api/src/main/java/com/bartlomiejpluta/base/api/util/path/RunSegment.java create mode 100644 api/src/main/java/com/bartlomiejpluta/base/api/util/path/TurnSegment.java create mode 100644 api/src/main/java/com/bartlomiejpluta/base/api/util/path/WaitSegment.java diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/game/ai/FollowPathAI.java b/api/src/main/java/com/bartlomiejpluta/base/api/game/ai/FollowPathAI.java index f6aea145..76e4b033 100644 --- a/api/src/main/java/com/bartlomiejpluta/base/api/game/ai/FollowPathAI.java +++ b/api/src/main/java/com/bartlomiejpluta/base/api/game/ai/FollowPathAI.java @@ -1,142 +1,52 @@ package com.bartlomiejpluta.base.api.game.ai; 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 java.util.LinkedList; -import java.util.List; +import com.bartlomiejpluta.base.api.util.path.NPCPath; +import com.bartlomiejpluta.base.api.util.path.PathExecutor; public class FollowPathAI implements AI { - private final List path = new LinkedList<>(); - private final NPC npc; - private final boolean repeat; - - private int current = 0; + private final PathExecutor executor; + private final NPCPath path; public FollowPathAI(NPC npc) { this(npc, true); } public FollowPathAI(NPC npc, boolean repeat) { - this.npc = npc; - this.repeat = repeat; + var path = new NPCPath(); + this.executor = new PathExecutor<>(npc, repeat, path); + this.path = path; } @Override public void nextActivity(ObjectLayer layer, float dt) { - if (!repeat && isRetired()) { - return; - } - - if (!npc.isMoving()) { - PathSegment item = (PathSegment) path.get(current % path.size()); - if (item.perform(npc, layer, dt)) { - ++current; - } - } + executor.execute(layer, dt); } - public boolean isRetired() { - return current == path.size() - 1; - } public FollowPathAI move(Direction direction) { - return move(direction, false); + path.move(direction); + return this; } public FollowPathAI move(Direction direction, boolean ignore) { - path.add(new Move(direction, ignore)); + path.move(direction, ignore); return this; } public FollowPathAI turn(Direction direction) { - path.add(new Turn(direction)); + path.turn(direction); return this; } public FollowPathAI wait(float seconds) { - path.add(new Wait(seconds)); + path.wait(seconds); return this; } public FollowPathAI run(Runnable runnable) { - path.add(new Run(runnable)); + path.run(runnable); 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; - } - } } diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/game/entity/Entity.java b/api/src/main/java/com/bartlomiejpluta/base/api/game/entity/Entity.java index c9cafc0d..a31b6c67 100644 --- a/api/src/main/java/com/bartlomiejpluta/base/api/game/entity/Entity.java +++ b/api/src/main/java/com/bartlomiejpluta/base/api/game/entity/Entity.java @@ -6,7 +6,7 @@ import com.bartlomiejpluta.base.api.internal.object.Placeable; import com.bartlomiejpluta.base.api.internal.render.Renderable; 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); Vector2ic getCoordinates(); @@ -15,10 +15,6 @@ public interface Entity extends Placeable, Renderable, Updatable { void setCoordinates(int x, int y); - Movement prepareMovement(Direction direction); - - Movement getMovement(); - Direction getFaceDirection(); void setFaceDirection(Direction direction); @@ -27,8 +23,6 @@ public interface Entity extends Placeable, Renderable, Updatable { void setAnimationSpeed(float speed); - boolean isMoving(); - int chebyshevDistance(Entity other); int manhattanDistance(Entity other); diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/game/entity/Movable.java b/api/src/main/java/com/bartlomiejpluta/base/api/game/entity/Movable.java new file mode 100644 index 00000000..6e7257d8 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/game/entity/Movable.java @@ -0,0 +1,9 @@ +package com.bartlomiejpluta.base.api.game.entity; + +public interface Movable { + Movement prepareMovement(Direction direction); + + Movement getMovement(); + + boolean isMoving(); +} diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/util/path/MoveSegment.java b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/MoveSegment.java new file mode 100644 index 00000000..39634668 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/MoveSegment.java @@ -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 implements PathSegment { + 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; + } +} diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/util/path/NPCPath.java b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/NPCPath.java new file mode 100644 index 00000000..d6cecde8 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/NPCPath.java @@ -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 { + + public NPCPath turn(Direction direction) { + path.add(new TurnSegment<>(direction)); + return this; + } +} diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/util/path/Path.java b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/Path.java new file mode 100644 index 00000000..9991d34c --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/Path.java @@ -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 { + protected final List> path = new ArrayList<>(); + + public List> getPath() { + return path; + } + + public Path add(PathSegment segment) { + path.add(segment); + return this; + } + + public Path move(Direction direction) { + path.add(new MoveSegment<>(direction)); + return this; + } + + public Path move(Direction direction, boolean ignore) { + path.add(new MoveSegment<>(direction, ignore)); + return this; + } + + public Path wait(float seconds) { + path.add(new WaitSegment<>(seconds)); + return this; + } + + public Path run(Runnable runnable) { + path.add(new RunSegment<>(runnable)); + return this; + } +} diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/util/path/PathExecutor.java b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/PathExecutor.java new file mode 100644 index 00000000..98d39294 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/PathExecutor.java @@ -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 { + protected final List> path; + private final T movable; + private final boolean repeat; + + private int current = 0; + + public PathExecutor(T movable, boolean repeat, Path 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; + } +} diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/util/path/PathSegment.java b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/PathSegment.java new file mode 100644 index 00000000..9db646eb --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/PathSegment.java @@ -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 { + boolean perform(T movable, ObjectLayer layer, float dt); +} diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/util/path/RunSegment.java b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/RunSegment.java new file mode 100644 index 00000000..028ec872 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/RunSegment.java @@ -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 implements PathSegment { + 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; + } +} diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/util/path/TurnSegment.java b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/TurnSegment.java new file mode 100644 index 00000000..cf56b379 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/TurnSegment.java @@ -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 implements PathSegment { + 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; + } +} diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/util/path/WaitSegment.java b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/WaitSegment.java new file mode 100644 index 00000000..030b4727 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/util/path/WaitSegment.java @@ -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 implements PathSegment { + 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; + } +}