Create KeepStraightDistanceAI AI strategy

This commit is contained in:
2022-08-18 22:36:46 +02:00
parent 05f55c482d
commit 44782d1328

View File

@@ -0,0 +1,167 @@
package com.bartlomiejpluta.base.lib.ai;
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.MoveEvent;
import com.bartlomiejpluta.base.util.path.MovementPath;
import com.bartlomiejpluta.base.util.path.PathExecutor;
import com.bartlomiejpluta.base.util.pathfinder.PathFinder;
import lombok.NonNull;
import org.joml.Vector2i;
import org.joml.Vector2ic;
import java.util.ArrayList;
public abstract class KeepStraightDistanceAI<N extends NPC, T extends Entity> implements AI {
private final N npc;
private final T target;
private final PathFinder finder;
private final PathExecutor<N> executor;
private final int minRange;
private final int maxRange;
private MovementPath<N> path = null;
public KeepStraightDistanceAI(@NonNull PathFinder finder, @NonNull N npc, @NonNull T target, int minRange, int maxRange) {
this.npc = npc;
this.target = target;
this.minRange = minRange;
this.maxRange = maxRange;
this.finder = finder;
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()) {
return;
}
if (!sees(npc, target, layer)) {
idle(npc, target, layer, dt);
}
if (path == null) {
// We are considering only straight positions against the target ("@"), for example
// when minRange is 3 and maxRange is 6, then we are considering only "O"-marked positions.
// The X means some obstacle for which we'd like to prune the positions after that:
// 5 4 3 2 1 0 1 2 3 4 5
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | | | | | | | | | | |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | O | | | | | | | | | |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | O | | | | | | | | | | 5
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | O | | | | | | | | | | 4
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | O | | | | | | | | | | 3
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | | | | | | | | | | | 2
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | | | | | | | | | | | 1
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | O | O | O | O | | | @ | | | O | X | | | | | | 0
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | | | | | | | | | | | 1
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | | | | | | | | | | | 2
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | O | | | | | | | | | | 3
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | O | | | | | | | | | | 4
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | O | | | | | | | | | | 5
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | O | | | | | | | | | |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
// | | | | | | | | | | | | | | | | | | |
// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
var consideredPositions = new ArrayList<Vector2ic>(4 * (maxRange - minRange + 1));
var x = target.getCoordinates().x();
var y = target.getCoordinates().y();
var right = true;
var up = true;
var left = true;
var down = true;
for (int i = 1; i <= maxRange; ++i) {
var vecUp = new Vector2i(x, y + i);
var vecRight = new Vector2i(x + i, y);
var vecDown = new Vector2i(x, y - i);
var vecLeft = new Vector2i(x - i, y);
// We are pruning the directions that are
// blocked by some obstacle.
// Apart from using layer.isTileReachable() method,
// we need also to make sure that if the method does return
// 'true' because the NPC itself is currently occupying given place,
// which is of course the desired situation.
// Without this check, we would reject this position from considered list
// and keep looking for another one which would end up with infinite loop.
if (up && !layer.isTileReachable(vecUp) && !vecUp.equals(npc.getCoordinates())) up = false;
if (right && !layer.isTileReachable(vecRight) && !vecRight.equals(npc.getCoordinates())) right = false;
if (down && !layer.isTileReachable(vecDown) && !vecDown.equals(npc.getCoordinates())) down = false;
if (left && !layer.isTileReachable(vecLeft) && !vecLeft.equals(npc.getCoordinates())) left = false;
if (i >= minRange && up) consideredPositions.add(vecUp);
if (i >= minRange && right) consideredPositions.add(vecRight);
if (i >= minRange && down) consideredPositions.add(vecDown);
if (i >= minRange && left) consideredPositions.add(vecLeft);
}
consideredPositions.sort(this::comparator);
// If we are already on any of considered position
// we abandon finding another path and start to interact
for (var position : consideredPositions) {
if (npc.getCoordinates().equals(position)) {
npc.setFaceDirection(npc.getDirectionTowards(this.target));
interact(npc, target, layer, dt);
return;
}
}
// Otherwise we try to find the best path
// basing on heuristically considered targets
for (var position : consideredPositions) {
if (layer.isTileReachable(position)) {
path = finder.findPath(layer, npc, position);
executor.setPath(path);
break;
}
}
}
// If no idle and no interact, it means we are following calculated path
follow(npc, target, layer, dt);
executor.execute(layer, dt);
}
protected abstract boolean sees(N npc, T target, ObjectLayer layer);
protected abstract void interact(N npc, T target, ObjectLayer layer, float dt);
protected abstract void follow(N npc, T target, ObjectLayer layer, float dt);
protected abstract void idle(N npc, T target, ObjectLayer layer, float dt);
private int comparator(Vector2ic a, Vector2ic b) {
return Integer.compare(npc.manhattanDistance(a), npc.manhattanDistance(b));
}
}