Documentation, cleanup, incremental resource usage, core seeking
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.Connection;
|
||||
import com.badlogic.gdx.ai.pfa.HierarchicalGraph;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
public class HGraph implements HierarchicalGraph<Tile> {
|
||||
|
||||
@Override
|
||||
public int getLevelCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLevel(int level) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tile convertNodeBetweenLevels(int inputLevel, Tile node, int outputLevel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Array<Connection<Tile>> getConnections(Tile fromNode) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.Heuristic;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.ucore.function.Predicate;
|
||||
|
||||
import static io.anuke.mindustry.Vars.tilesize;
|
||||
|
||||
public class Heuristics {
|
||||
/**How many times more it costs to go through a destructible block than an empty block.*/
|
||||
static final float solidMultiplier = 5f;
|
||||
/**How many times more it costs to go through a tile that touches a solid block.*/
|
||||
static final float occludedMultiplier = 5f;
|
||||
|
||||
/**Calculates the fastest path. No priorities, just avoids solid blocks.*/
|
||||
public static class FastestHeuristic implements Heuristic<Tile> {
|
||||
|
||||
@Override
|
||||
public float estimate(Tile node, Tile other){
|
||||
//Get Manhattan distance cost
|
||||
float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy());
|
||||
|
||||
//If either one of the tiles is a breakable solid block (that is, it's player-made),
|
||||
//increase the cost by the tilesize times the solid block multiplier
|
||||
//Also add the block health, so blocks with more health cost more to traverse
|
||||
if(node.breakable() && node.block().solid) cost += tilesize* solidMultiplier + node.block().health;
|
||||
if(other.breakable() && other.block().solid) cost += tilesize* solidMultiplier + other.block().health;
|
||||
|
||||
//if this block has solid blocks near it, increase the cost, as we don't want enemies hugging walls
|
||||
//if(node.occluded) cost += tilesize*occludedMultiplier;
|
||||
|
||||
return cost;
|
||||
}
|
||||
}
|
||||
|
||||
/**Calculates the fastest and most destructive path based on a block predicate.*/
|
||||
public static class DestrutiveHeuristic implements Heuristic<Tile> {
|
||||
/**Should return whether a block if "free", e.g. whether it's an important target*/
|
||||
private final Predicate<Block> frees;
|
||||
|
||||
public DestrutiveHeuristic(Predicate<Block> frees){
|
||||
this.frees = frees;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float estimate(Tile node, Tile other){
|
||||
//Get Manhattan distance cost
|
||||
float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy());
|
||||
|
||||
//If either one of the tiles is a breakable solid block (that is, it's player-made),
|
||||
//increase the cost by the tilesize times the solid block multiplier
|
||||
//Also add the block health, so blocks with more health cost more to traverse
|
||||
if(node.breakable() && node.block().solid) cost += tilesize* solidMultiplier + node.block().health;
|
||||
if(other.breakable() && other.block().solid) cost += tilesize* solidMultiplier + other.block().health;
|
||||
|
||||
//if this block has solid blocks near it, increase the cost, as we don't want enemies hugging walls
|
||||
//if(node.occluded) cost += tilesize*occludedMultiplier;
|
||||
|
||||
if(other.getLinked() != null) other = other.getLinked();
|
||||
if(node.getLinked() != null) node = node.getLinked();
|
||||
|
||||
//check if it's free
|
||||
if(frees.test(other.block()) || frees.test(node.block())) cost = 0;
|
||||
|
||||
return cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
/**An interface for an indexed graph that doesn't use allocations for connections.*/
|
||||
public interface OptimizedGraph<N>{
|
||||
/**This is used in the same way as getConnections(), but does not use Connection objects.*/
|
||||
N[] connectionsOf(N node);
|
||||
|
||||
/** Returns the unique index of the given node.
|
||||
* @param node the node whose index will be returned
|
||||
* @return the unique index of the given node. */
|
||||
int getIndex (N node);
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.GraphPath;
|
||||
import com.badlogic.gdx.ai.pfa.PathFinderQueue;
|
||||
import com.badlogic.gdx.ai.pfa.PathFinderRequest;
|
||||
import com.badlogic.gdx.utils.BinaryHeap;
|
||||
import com.badlogic.gdx.utils.IntMap;
|
||||
import com.badlogic.gdx.utils.TimeUtils;
|
||||
import io.anuke.mindustry.content.fx.Fx;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.ucore.core.Effects;
|
||||
import io.anuke.ucore.function.Consumer;
|
||||
import io.anuke.ucore.util.Geometry;
|
||||
import io.anuke.ucore.util.Mathf;
|
||||
|
||||
/**An IndexedAStarPathfinder that uses an OptimizedGraph, and therefore has less allocations.*/
|
||||
public class OptimizedPathFinder {
|
||||
IntMap<NodeRecord> records = new IntMap<>();
|
||||
BinaryHeap<NodeRecord> openList;
|
||||
NodeRecord current;
|
||||
|
||||
private int searchId;
|
||||
private Tile end;
|
||||
|
||||
private static final byte UNVISITED = 0;
|
||||
private static final byte OPEN = 1;
|
||||
private static final byte CLOSED = 2;
|
||||
|
||||
private static final boolean debug = false;
|
||||
|
||||
public OptimizedPathFinder() {
|
||||
this.openList = new BinaryHeap<>();
|
||||
}
|
||||
|
||||
public boolean searchNodePath(Tile startNode, Tile endNode, GraphPath<Tile> outPath) {
|
||||
this.end = endNode;
|
||||
|
||||
// Perform AStar
|
||||
boolean found = search(startNode, endNode);
|
||||
|
||||
if (found) {
|
||||
// Create a path made of nodes
|
||||
generateNodePath(startNode, outPath);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
protected boolean search(Tile startNode, Tile endNode) {
|
||||
|
||||
initSearch(startNode, endNode);
|
||||
|
||||
// Iterate through processing each node
|
||||
do {
|
||||
// Retrieve the node with smallest estimated total cost from the open list
|
||||
current = openList.pop();
|
||||
current.category = CLOSED;
|
||||
|
||||
// Terminate if we reached the goal node
|
||||
if (current.node == endNode) return true;
|
||||
|
||||
visitChildren(endNode);
|
||||
|
||||
} while (openList.size > 0);
|
||||
|
||||
// We've run out of nodes without finding the goal, so there's no solution
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean search(PathFinderRequest<Tile> request, long timeToRun) {
|
||||
|
||||
long lastTime = TimeUtils.nanoTime();
|
||||
|
||||
// We have to initialize the search if the status has just changed
|
||||
if (request.statusChanged) {
|
||||
initSearch(request.startNode, request.endNode);
|
||||
request.statusChanged = false;
|
||||
}
|
||||
|
||||
// Iterate through processing each node
|
||||
do {
|
||||
|
||||
// Check the available time
|
||||
long currentTime = TimeUtils.nanoTime();
|
||||
timeToRun -= currentTime - lastTime;
|
||||
if (timeToRun <= PathFinderQueue.TIME_TOLERANCE) return false;
|
||||
|
||||
// Retrieve the node with smallest estimated total cost from the open list
|
||||
current = openList.pop();
|
||||
current.category = CLOSED;
|
||||
|
||||
// Terminate if we reached the goal node; we've found a path.
|
||||
if (current.node == request.endNode) {
|
||||
request.pathFound = true;
|
||||
|
||||
generateNodePath(request.startNode, request.resultPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Visit current node's children
|
||||
visitChildren(request.endNode);
|
||||
|
||||
// Store the current time
|
||||
lastTime = currentTime;
|
||||
|
||||
} while (openList.size > 0);
|
||||
|
||||
// The open list is empty and we've not found a path.
|
||||
request.pathFound = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void initSearch(Tile startNode, Tile endNode) {
|
||||
|
||||
// Increment the search id
|
||||
if (++searchId < 0) searchId = 1;
|
||||
|
||||
// Initialize the open list
|
||||
openList.clear();
|
||||
|
||||
// Initialize the record for the start node and add it to the open list
|
||||
NodeRecord startRecord = getNodeRecord(startNode);
|
||||
startRecord.node = startNode;
|
||||
//startRecord.connection = null;
|
||||
startRecord.costSoFar = 0;
|
||||
addToOpenList(startRecord, estimate(startNode, endNode));
|
||||
|
||||
current = null;
|
||||
}
|
||||
|
||||
protected void visitChildren(Tile endNode) {
|
||||
if(debug) Effects.effect(Fx.node3, current.node.worldx(), current.node.worldy());
|
||||
|
||||
nodes(current.node, node -> {
|
||||
float addCost = estimate(current.node, node);
|
||||
|
||||
float nodeCost = current.costSoFar + addCost;
|
||||
|
||||
float nodeHeuristic;
|
||||
NodeRecord nodeRecord = getNodeRecord(node);
|
||||
|
||||
if (nodeRecord.category == CLOSED) { // The node is closed
|
||||
|
||||
// If we didn't find a shorter route, skip
|
||||
if (nodeRecord.costSoFar <= nodeCost){
|
||||
return;
|
||||
}
|
||||
|
||||
// We can use the node's old cost values to calculate its heuristic
|
||||
// without calling the possibly expensive heuristic function
|
||||
nodeHeuristic = nodeRecord.getEstimatedTotalCost() - nodeRecord.costSoFar;
|
||||
} else if (nodeRecord.category == OPEN) { // The node is open
|
||||
|
||||
//If our route is no better, then skip
|
||||
if (nodeRecord.costSoFar <= nodeCost){
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove it from the open list (it will be re-added with the new cost)
|
||||
openList.remove(nodeRecord);
|
||||
|
||||
// We can use the node's old cost values to calculate its heuristic
|
||||
// without calling the possibly expensive heuristic function
|
||||
nodeHeuristic = nodeRecord.getEstimatedTotalCost() - nodeRecord.costSoFar;
|
||||
} else { // the node is unvisited
|
||||
|
||||
// We'll need to calculate the heuristic value using the function,
|
||||
// since we don't have a node record with a previously calculated value
|
||||
nodeHeuristic = estimate(node, endNode);
|
||||
}
|
||||
|
||||
// Update node record's cost and connection
|
||||
nodeRecord.costSoFar = nodeCost;
|
||||
nodeRecord.from = current.node;
|
||||
|
||||
// Add it to the open list with the estimated total cost
|
||||
addToOpenList(nodeRecord, nodeCost + nodeHeuristic);
|
||||
});
|
||||
}
|
||||
|
||||
protected void nodes(Tile current, Consumer<Tile> cons){
|
||||
if(obstacle(current)) return;
|
||||
for(int i = 0; i < 4; i ++){
|
||||
Tile n = current.getNearby(i);
|
||||
if(!obstacle(n)) cons.accept(n);
|
||||
}
|
||||
}
|
||||
|
||||
protected void jps(Tile current, int direction, Tile end, Consumer<Tile> cons){
|
||||
if(obstacle(current)) return; //skip solid or off-the-screen stuff
|
||||
|
||||
//if there's no start point, scan everything.
|
||||
if(direction == -1){
|
||||
for(int i = 0; i < 8; i ++){
|
||||
jps(current.getNearby(Geometry.d8[i]), i, end, cons);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(direction % 2 == 0){
|
||||
//forced neighbor in the straight pattern
|
||||
if(obstacle(rel(current, direction + 2)) && !obstacle(rel(current, direction + 1))){
|
||||
cons.accept(rel(current, direction + 1));
|
||||
}
|
||||
|
||||
if(obstacle(rel(current, direction - 2)) && !obstacle(rel(current, direction - 1))){
|
||||
cons.accept(rel(current, direction - 1));
|
||||
}
|
||||
}else{ //moving diagonal
|
||||
//forced neighbor in the diagonal pattern
|
||||
if(obstacle(rel(current, direction + 3)) && !obstacle(rel(current, direction + 2)) && !obstacle(rel(current, direction -2))) {
|
||||
cons.accept(rel(current, direction + 2));
|
||||
}
|
||||
|
||||
if(obstacle(rel(current, direction - 3)) && !obstacle(rel(current, direction - 2))&& !obstacle(rel(current, direction + 2))){
|
||||
cons.accept(rel(current, direction - 2));
|
||||
}
|
||||
}
|
||||
|
||||
while(!obstacle(current) && !trap(current, direction)){
|
||||
if(debug) Effects.effect(Fx.node1, current.worldx(), current.worldy());
|
||||
//moving straight
|
||||
if(direction % 2 == 0){
|
||||
Tile sf = scanDir(rel(current, direction), end, direction); //check if there's anything of interest going straight
|
||||
if(sf != null){ //if there is, jump to that location immediately and stop. else, nothing must be there, end.
|
||||
cons.accept(sf);
|
||||
}
|
||||
return;
|
||||
}else{ //moving diagonal
|
||||
Tile sl = scanDir(rel(current, Mathf.mod(direction - 1, 8)), end, Mathf.mod(direction - 1, 8));
|
||||
|
||||
if(sl != null){
|
||||
cons.accept(sl);
|
||||
}
|
||||
|
||||
Tile sr = scanDir(rel(current, Mathf.mod(direction + 1, 8)), end, Mathf.mod(direction + 1, 8));
|
||||
|
||||
if(sr != null){
|
||||
cons.accept(sr);
|
||||
}
|
||||
|
||||
Tile sf = scanDir(rel(current, direction), end, direction);
|
||||
|
||||
if(sf != null){
|
||||
cons.accept(sf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(current == end){
|
||||
cons.accept(end);
|
||||
return;
|
||||
}
|
||||
|
||||
current = rel(current, direction);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean trap(Tile tile, int direction){
|
||||
return direction % 2 == 1 && obstacle(rel(tile, direction - 1)) && obstacle(rel(tile, direction + 1));
|
||||
}
|
||||
|
||||
protected Tile scanDir(Tile tile, Tile end, int direction){
|
||||
while(!obstacle(tile)){
|
||||
if(debug) Effects.effect(Fx.node2, tile.worldx(), tile.worldy());
|
||||
if(tile == end) return tile;
|
||||
if(direction % 2 == 0){
|
||||
|
||||
//forced neighbor in the straight pattern
|
||||
if((obstacle(rel(tile, direction + 2)) && !obstacle(rel(tile, direction + 1))) ||
|
||||
(obstacle(rel(tile, direction - 2)) && !obstacle(rel(tile, direction - 1)))){
|
||||
//Log.info("Found forced linear neighbor {0} {1} // {2}", tile.x, tile.y, direction);
|
||||
if(debug) Effects.effect(Fx.node4, tile.worldx(), tile.worldy());
|
||||
return tile;
|
||||
}
|
||||
}else{ //moving diagonal
|
||||
//forced neighbor in the diagonal pattern, end here
|
||||
if((obstacle(rel(tile, direction + 3)) && !obstacle(rel(tile, direction + 2)) && !obstacle(rel(tile, direction - 2))) ||
|
||||
(obstacle(rel(tile, direction - 3)) && !obstacle(rel(tile, direction - 2)) && !obstacle(rel(tile, direction + 2)))) {
|
||||
if(debug) Effects.effect(Fx.node4, tile.worldx(), tile.worldy());
|
||||
//Log.info("Found forced diagonal neighbor {0} {1} // {2}", tile.x, tile.y, direction);
|
||||
return tile;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Tile next = rel(tile, direction);
|
||||
if(obstacle(next)) break;
|
||||
tile = next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Tile rel(Tile tile, int i){
|
||||
return tile.getNearby(Geometry.d8[Mathf.mod(i, 8)]);
|
||||
}
|
||||
|
||||
protected boolean obstacle(Tile tile){
|
||||
return tile == null || (tile.solid() && end.target() != tile && tile.target() != end);
|
||||
}
|
||||
|
||||
protected float estimate(Tile tile, Tile other){
|
||||
return Math.abs(tile.worldx() - other.worldx()) + Math.abs(tile.worldy() - other.worldy()) +0;
|
||||
// (tile.occluded ? tilesize : 0) + (other.occluded ? tilesize : 0);
|
||||
}
|
||||
|
||||
protected int relDirection(Tile from, Tile current){
|
||||
if(from.y == current.y && from.x > current.x) return 0;
|
||||
if(from.y == current.y && from.x < current.x) return 4;
|
||||
if(from.x == current.x && from.y > current.y) return 2;
|
||||
if(from.x == current.x && from.y < current.y) return 6;
|
||||
|
||||
if(from.y > current.y && from.x > current.x) return 1;
|
||||
if(from.y < current.y && from.x < current.x) return 5;
|
||||
if(from.x > current.x && from.y < current.y) return 7;
|
||||
if(from.x < current.x && from.y > current.y) return 3;
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected void generateNodePath(Tile startNode, GraphPath<Tile> outPath) {
|
||||
|
||||
// Work back along the path, accumulating nodes
|
||||
// outPath.clear();
|
||||
while (current.from != null) {
|
||||
outPath.add(current.node);
|
||||
current = records.get(indexOf(current.from));
|
||||
}
|
||||
outPath.add(startNode);
|
||||
|
||||
// Reverse the path
|
||||
outPath.reverse();
|
||||
}
|
||||
|
||||
protected void addToOpenList(NodeRecord nodeRecord, float estimatedTotalCost) {
|
||||
openList.add(nodeRecord, estimatedTotalCost);
|
||||
nodeRecord.category = OPEN;
|
||||
}
|
||||
|
||||
protected NodeRecord getNodeRecord(Tile node) {
|
||||
if(!records.containsKey(indexOf(node))){
|
||||
NodeRecord record = new NodeRecord();
|
||||
record.node = node;
|
||||
record.searchId = searchId;
|
||||
records.put(indexOf(node), record);
|
||||
return record;
|
||||
}else{
|
||||
NodeRecord record = records.get(indexOf(node));
|
||||
if(record.searchId != searchId){
|
||||
record.category = UNVISITED;
|
||||
record.searchId = searchId;
|
||||
}
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
||||
private int indexOf(Tile node){
|
||||
return node.packedPosition();
|
||||
}
|
||||
|
||||
static class NodeRecord extends BinaryHeap.Node {
|
||||
Tile node;
|
||||
Tile from;
|
||||
|
||||
float costSoFar;
|
||||
byte category;
|
||||
|
||||
int searchId;
|
||||
|
||||
public NodeRecord() {
|
||||
super(0);
|
||||
}
|
||||
|
||||
public float getEstimatedTotalCost() {
|
||||
return getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.utils.Collision;
|
||||
import com.badlogic.gdx.ai.utils.Ray;
|
||||
import com.badlogic.gdx.ai.utils.RaycastCollisionDetector;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.ucore.util.Geometry;
|
||||
import io.anuke.ucore.util.Mathf;
|
||||
|
||||
import static io.anuke.mindustry.Vars.tilesize;
|
||||
import static io.anuke.mindustry.Vars.world;
|
||||
|
||||
public class Raycaster implements RaycastCollisionDetector<Vector2>{
|
||||
private boolean found = false;
|
||||
|
||||
@Override
|
||||
public boolean collides(Ray<Vector2> ray){
|
||||
found = false;
|
||||
|
||||
Geometry.iterateLine(0f, ray.start.x, ray.start.y, ray.end.x, ray.end.y, tilesize, (x, y)->{
|
||||
if(solid(x, y)){
|
||||
found = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean findCollision(Collision<Vector2> collision, Ray<Vector2> ray){
|
||||
Vector2 v = vectorCast(ray.start.x, ray.start.y, ray.end.x, ray.end.y);
|
||||
if(v == null) return false;
|
||||
collision.point = v;
|
||||
collision.normal = v.nor();
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector2 vectorCast(float x0f, float y0f, float x1f, float y1f){
|
||||
int x0 = (int)x0f;
|
||||
int y0 = (int)y0f;
|
||||
int x1 = (int)x1f;
|
||||
int y1 = (int)y1f;
|
||||
int dx = Math.abs(x1 - x0);
|
||||
int dy = Math.abs(y1 - y0);
|
||||
|
||||
int sx = x0 < x1 ? 1 : -1;
|
||||
int sy = y0 < y1 ? 1 : -1;
|
||||
|
||||
int err = dx - dy;
|
||||
int e2;
|
||||
while(true){
|
||||
|
||||
if(solid(x0, y0)){
|
||||
return new Vector2(x0, y0);
|
||||
}
|
||||
if(x0 == x1 && y0 == y1) break;
|
||||
|
||||
e2 = 2 * err;
|
||||
if(e2 > -dy){
|
||||
err = err - dy;
|
||||
x0 = x0 + sx;
|
||||
}
|
||||
|
||||
if(e2 < dx){
|
||||
err = err + dx;
|
||||
y0 = y0 + sy;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean solid(float x, float y){
|
||||
Tile tile = world.tile(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize));
|
||||
|
||||
if(tile == null || tile.solid()) return true;
|
||||
|
||||
for(int i = 0; i < 4; i ++){
|
||||
Tile near = tile.getNearby(i);
|
||||
if(near == null || near.solid()) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.DefaultGraphPath;
|
||||
import com.badlogic.gdx.ai.pfa.SmoothableGraphPath;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
public class SmoothGraphPath extends DefaultGraphPath<Tile> implements SmoothableGraphPath<Tile, Vector2>{
|
||||
private Vector2 vector = new Vector2();
|
||||
|
||||
@Override
|
||||
public Vector2 getNodePosition(int index){
|
||||
Tile tile = nodes.get(index);
|
||||
return vector.set(tile.worldx(), tile.worldy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swapNodes(int index1, int index2){
|
||||
nodes.swap(index1, index2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void truncatePath(int newLength){
|
||||
nodes.truncate(newLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add (Tile node) {
|
||||
nodes.add(node);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
/**Tilegraph that ignores player-made tiles.*/
|
||||
public class TileGraph implements OptimizedGraph<Tile> {
|
||||
private Tile[] tiles = new Tile[4];
|
||||
|
||||
/**Used for the OptimizedPathFinder implementation.*/
|
||||
@Override
|
||||
public Tile[] connectionsOf(Tile node){
|
||||
Tile[] nodes = node.getNearby(tiles);
|
||||
for(int i = 0; i < 4; i ++){
|
||||
if(nodes[i] != null && !nodes[i].passable()){
|
||||
nodes[i] = null;
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndex(Tile node){
|
||||
return node.packedPosition();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user