mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-02-07 17:34:13 +07:00
Optimization, refactoring, documentation
This commit is contained in:
parent
ba2cfc2820
commit
1f9a92cf32
35
core/src/io/anuke/mindustry/ai/HueristicImpl.java
Normal file
35
core/src/io/anuke/mindustry/ai/HueristicImpl.java
Normal file
@ -0,0 +1,35 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.Heuristic;
|
||||
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
public class HueristicImpl implements Heuristic<Tile>{
|
||||
/**How many times more it costs to go through a destructible block than an empty block.*/
|
||||
static final float solidMultiplier = 10f;
|
||||
/**How many times more it costs to go through a tile that touches a solid block.*/
|
||||
static final float occludedMultiplier = 5f;
|
||||
|
||||
@Override
|
||||
public float estimate(Tile node, Tile other){
|
||||
return estimateStatic(node, other);
|
||||
}
|
||||
|
||||
/**Estimate the cost of walking between two tiles.*/
|
||||
public static float estimateStatic(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 multiplayer
|
||||
if(node.breakable() && node.block().solid) cost += Vars.tilesize* solidMultiplier;
|
||||
if(other.breakable() && other.block().solid) cost += Vars.tilesize* solidMultiplier;
|
||||
|
||||
//if this block has solid blocks near it, increase the cost, as we don't want enemies hugging walls
|
||||
if(node.occluded) cost += Vars.tilesize*occludedMultiplier;
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.Heuristic;
|
||||
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
public class MHueristic implements Heuristic<Tile>{
|
||||
//so this means that the cost of going through solids is 10x going through non solids
|
||||
static float multiplier = 10f;
|
||||
|
||||
@Override
|
||||
public float estimate(Tile node, Tile other){
|
||||
return estimateStatic(node, other);
|
||||
}
|
||||
|
||||
public static float estimateStatic(Tile node, Tile other){
|
||||
float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy());
|
||||
|
||||
//TODO balance multiplier
|
||||
if(node.breakable() && node.block().solid) cost += Vars.tilesize*multiplier;
|
||||
if(other.breakable() && other.block().solid) cost += Vars.tilesize*multiplier;
|
||||
for(int dx = -1; dx <= 1; dx ++){
|
||||
for(int dy = -1; dy <= 1; dy ++){
|
||||
Tile tile = Vars.world.tile(node.x + dx, node.y + dy);
|
||||
if(tile != null && tile.solid()){
|
||||
cost += Vars.tilesize*5;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
||||
}
|
9
core/src/io/anuke/mindustry/ai/OptimizedGraph.java
Normal file
9
core/src/io/anuke/mindustry/ai/OptimizedGraph.java
Normal file
@ -0,0 +1,9 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.indexed.IndexedGraph;
|
||||
|
||||
/**An interface for an indexed graph that doesn't use allocations for connections.*/
|
||||
public interface OptimizedGraph<N> extends IndexedGraph<N> {
|
||||
/**This is used in the same way as getConnections(), but does not use Connection objects.*/
|
||||
public N[] connectionsOf(N node);
|
||||
}
|
295
core/src/io/anuke/mindustry/ai/OptimizedPathFinder.java
Normal file
295
core/src/io/anuke/mindustry/ai/OptimizedPathFinder.java
Normal file
@ -0,0 +1,295 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.*;
|
||||
import com.badlogic.gdx.utils.BinaryHeap;
|
||||
import com.badlogic.gdx.utils.TimeUtils;
|
||||
|
||||
/**An IndexedAStarPathfinder that uses an OptimizedGraph, and therefore has less allocations.*/
|
||||
public class OptimizedPathFinder<N> implements PathFinder<N> {
|
||||
OptimizedGraph<N> graph;
|
||||
NodeRecord<N>[] nodeRecords;
|
||||
BinaryHeap<NodeRecord<N>> openList;
|
||||
NodeRecord<N> current;
|
||||
|
||||
/**
|
||||
* The unique ID for each search run. Used to mark nodes.
|
||||
*/
|
||||
private int searchId;
|
||||
|
||||
private static final byte UNVISITED = 0;
|
||||
private static final byte OPEN = 1;
|
||||
private static final byte CLOSED = 2;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public OptimizedPathFinder(OptimizedGraph<N> graph) {
|
||||
this.graph = graph;
|
||||
this.nodeRecords = (NodeRecord<N>[]) new NodeRecord[graph.getNodeCount()];
|
||||
this.openList = new BinaryHeap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean searchConnectionPath(N startNode, N endNode, Heuristic<N> heuristic, GraphPath<Connection<N>> outPath) {
|
||||
|
||||
// Perform AStar
|
||||
boolean found = search(startNode, endNode, heuristic);
|
||||
|
||||
if (found) {
|
||||
// Create a path made of connections
|
||||
generateConnectionPath(startNode, outPath);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean searchNodePath(N startNode, N endNode, Heuristic<N> heuristic, GraphPath<N> outPath) {
|
||||
|
||||
// Perform AStar
|
||||
boolean found = search(startNode, endNode, heuristic);
|
||||
|
||||
if (found) {
|
||||
// Create a path made of nodes
|
||||
generateNodePath(startNode, outPath);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
protected boolean search(N startNode, N endNode, Heuristic<N> heuristic) {
|
||||
|
||||
initSearch(startNode, endNode, heuristic);
|
||||
|
||||
// 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, heuristic);
|
||||
|
||||
} while (openList.size > 0);
|
||||
|
||||
// We've run out of nodes without finding the goal, so there's no solution
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean search(PathFinderRequest<N> 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.heuristic);
|
||||
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, request.heuristic);
|
||||
|
||||
// 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(N startNode, N endNode, Heuristic<N> heuristic) {
|
||||
|
||||
// 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<N> startRecord = getNodeRecord(startNode);
|
||||
startRecord.node = startNode;
|
||||
//startRecord.connection = null;
|
||||
startRecord.costSoFar = 0;
|
||||
addToOpenList(startRecord, heuristic.estimate(startNode, endNode));
|
||||
|
||||
current = null;
|
||||
}
|
||||
|
||||
protected void visitChildren(N endNode, Heuristic<N> heuristic) {
|
||||
// Get current node's outgoing connections
|
||||
//Array<Connection<N>> connections = graph.getConnections(current.node);
|
||||
N[] conn = graph.connectionsOf(current.node);
|
||||
|
||||
// Loop through each connection in turn
|
||||
for (int i = 0; i < conn.length; i++) {
|
||||
|
||||
//Connection<N> connection = connections.get(i)
|
||||
|
||||
// Get the cost estimate for the node
|
||||
N node = conn[i];
|
||||
|
||||
if(node == null) continue;
|
||||
|
||||
float addCost = heuristic.estimate(current.node, node);
|
||||
|
||||
float nodeCost = current.costSoFar + addCost;
|
||||
|
||||
float nodeHeuristic;
|
||||
NodeRecord<N> nodeRecord = getNodeRecord(node);
|
||||
if (nodeRecord.category == CLOSED) { // The node is closed
|
||||
|
||||
// If we didn't find a shorter route, skip
|
||||
if (nodeRecord.costSoFar <= nodeCost) continue;
|
||||
|
||||
// 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) continue;
|
||||
|
||||
// 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 = heuristic.estimate(node, endNode);
|
||||
}
|
||||
|
||||
// Update node record's cost and connection
|
||||
nodeRecord.costSoFar = nodeCost;
|
||||
nodeRecord.from = current.node; //TODO ???
|
||||
|
||||
// Add it to the open list with the estimated total cost
|
||||
addToOpenList(nodeRecord, nodeCost + nodeHeuristic);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void generateConnectionPath(N startNode, GraphPath<Connection<N>> outPath) {
|
||||
//do ABSOLUTELY NOTHING
|
||||
/*
|
||||
// Work back along the path, accumulating connections
|
||||
// outPath.clear();
|
||||
while (current.node != startNode) {
|
||||
outPath.add(current.connection);
|
||||
current = nodeRecords[graph.getIndex(current.connection.getFromNode())];
|
||||
}
|
||||
|
||||
// Reverse the path
|
||||
outPath.reverse();*/
|
||||
}
|
||||
|
||||
protected void generateNodePath(N startNode, GraphPath<N> outPath) {
|
||||
|
||||
// Work back along the path, accumulating nodes
|
||||
// outPath.clear();
|
||||
while (current.from != null) {
|
||||
outPath.add(current.node);
|
||||
current = nodeRecords[graph.getIndex(current.from)];
|
||||
}
|
||||
outPath.add(startNode);
|
||||
|
||||
// Reverse the path
|
||||
outPath.reverse();
|
||||
}
|
||||
|
||||
protected void addToOpenList(NodeRecord<N> nodeRecord, float estimatedTotalCost) {
|
||||
openList.add(nodeRecord, estimatedTotalCost);
|
||||
nodeRecord.category = OPEN;
|
||||
}
|
||||
|
||||
protected NodeRecord<N> getNodeRecord(N node) {
|
||||
int index = graph.getIndex(node);
|
||||
NodeRecord<N> nr = nodeRecords[index];
|
||||
if (nr != null) {
|
||||
if (nr.searchId != searchId) {
|
||||
nr.category = UNVISITED;
|
||||
nr.searchId = searchId;
|
||||
}
|
||||
return nr;
|
||||
}
|
||||
nr = nodeRecords[index] = new NodeRecord<>();
|
||||
nr.node = node;
|
||||
nr.searchId = searchId;
|
||||
return nr;
|
||||
}
|
||||
|
||||
/**
|
||||
* This nested class is used to keep track of the information we need for each node during the search.
|
||||
*
|
||||
* @param <N> Type of node
|
||||
* @author davebaol
|
||||
*/
|
||||
static class NodeRecord<N> extends BinaryHeap.Node {
|
||||
/**
|
||||
* The reference to the node.
|
||||
*/
|
||||
N node;
|
||||
N from;
|
||||
|
||||
/**
|
||||
* The incoming connection to the node
|
||||
*/
|
||||
//Connection<N> connection;
|
||||
|
||||
/**
|
||||
* The actual cost from the start node.
|
||||
*/
|
||||
float costSoFar;
|
||||
|
||||
/**
|
||||
* The node category: {@link #UNVISITED}, {@link #OPEN} or {@link #CLOSED}.
|
||||
*/
|
||||
byte category;
|
||||
|
||||
/**
|
||||
* ID of the current search.
|
||||
*/
|
||||
int searchId;
|
||||
|
||||
/**
|
||||
* Creates a {@code NodeRecord}.
|
||||
*/
|
||||
public NodeRecord() {
|
||||
super(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the estimated total cost.
|
||||
*/
|
||||
public float getEstimatedTotalCost() {
|
||||
return getValue();
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.PathFinderRequest;
|
||||
import com.badlogic.gdx.ai.pfa.PathSmoother;
|
||||
import com.badlogic.gdx.ai.pfa.indexed.IndexedAStarPathFinder;
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import io.anuke.mindustry.Vars;
|
||||
@ -17,14 +16,24 @@ import io.anuke.ucore.util.Mathf;
|
||||
import io.anuke.ucore.util.Tmp;
|
||||
|
||||
public class Pathfind{
|
||||
private static final long ms = 1000000 * 500;
|
||||
|
||||
MHueristic heuristic = new MHueristic();
|
||||
PassTileGraph graph = new PassTileGraph();
|
||||
/**Maximum time taken per frame on pathfinding for a single path.*/
|
||||
private static final long maxTime = 1000000 * 5;
|
||||
|
||||
/**Heuristic for determining cost between two tiles*/
|
||||
HueristicImpl heuristic = new HueristicImpl();
|
||||
/**Tile graph, for determining conenctions between two tiles*/
|
||||
TileGraph graph = new TileGraph();
|
||||
/**Smoother that removes extra nodes from a path.*/
|
||||
PathSmoother<Tile, Vector2> smoother = new PathSmoother<Tile, Vector2>(new Raycaster());
|
||||
/**temporary vector2 for calculations*/
|
||||
Vector2 vector = new Vector2();
|
||||
|
||||
|
||||
/**Finds the position on the path an enemy should move to.
|
||||
* If the path is not yet calculated, this returns the enemy's position (i. e. "don't move")
|
||||
* @param enemy The enemy to find a path for
|
||||
* @return The position the enemy should move to.*/
|
||||
public Vector2 find(Enemy enemy){
|
||||
//TODO fix -1/-2 node usage
|
||||
if(enemy.node == -1 || enemy.node == -2){
|
||||
findNode(enemy);
|
||||
}
|
||||
@ -38,9 +47,10 @@ public class Pathfind{
|
||||
}
|
||||
|
||||
Tile[] path = Vars.control.getSpawnPoints().get(enemy.lane).pathTiles;
|
||||
|
||||
|
||||
//if an enemy is idle for a while, it's probably stuck
|
||||
if(enemy.idletime > EnemyType.maxIdle){
|
||||
//TODO reverse
|
||||
|
||||
Tile target = path[enemy.node];
|
||||
if(Vars.world.raycastWorld(enemy.x, enemy.y, target.worldx(), target.worldy()) != null) {
|
||||
if (enemy.node > 1)
|
||||
@ -49,7 +59,6 @@ public class Pathfind{
|
||||
}
|
||||
|
||||
//else, must be blocked by a playermade block, do nothing
|
||||
|
||||
}
|
||||
|
||||
//-1 is only possible here if both pathfindings failed, which should NOT happen
|
||||
@ -58,7 +67,8 @@ public class Pathfind{
|
||||
if(enemy.node <= -1){
|
||||
return vector.set(enemy.x, enemy.y);
|
||||
}
|
||||
|
||||
|
||||
//TODO documentation on what this does
|
||||
Tile prev = path[enemy.node - 1];
|
||||
|
||||
Tile target = path[enemy.node];
|
||||
@ -103,14 +113,16 @@ public class Pathfind{
|
||||
return vector;
|
||||
|
||||
}
|
||||
|
||||
public void update(){
|
||||
int index = 0;
|
||||
|
||||
/**Update the pathfinders and continue calculating the path if it hasn't been calculated yet.
|
||||
* This method is run each frame.*/
|
||||
public void update(){
|
||||
|
||||
//go through each spawnpoint, and if it's not found a path yet, update it
|
||||
for(SpawnPoint point : Vars.control.getSpawnPoints()){
|
||||
if(!point.request.pathFound){
|
||||
try{
|
||||
if(point.finder.search(point.request, ms)){
|
||||
if(point.finder.search(point.request, maxTime)){
|
||||
smoother.smoothPath(point.path);
|
||||
point.pathTiles = point.path.nodes.toArray(Tile.class);
|
||||
}
|
||||
@ -119,12 +131,12 @@ public class Pathfind{
|
||||
point.request.pathFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
index ++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//1300-1500ms, usually 1400 unoptimized
|
||||
/**Benchmark pathfinding speed. Debugging stuff.*/
|
||||
public void benchmark(){
|
||||
SpawnPoint point = Vars.control.getSpawnPoints().first();
|
||||
|
||||
@ -141,29 +153,22 @@ public class Pathfind{
|
||||
}
|
||||
UCore.log("Time elapsed: " + Timers.elapsed() + "ms");
|
||||
}
|
||||
|
||||
public boolean finishedUpdating(){
|
||||
|
||||
/**Reset and clear the paths.*/
|
||||
public void resetPaths(){
|
||||
for(SpawnPoint point : Vars.control.getSpawnPoints()){
|
||||
if(point.pathTiles == null){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updatePath(){
|
||||
for(SpawnPoint point : Vars.control.getSpawnPoints()){
|
||||
point.finder = new IndexedAStarPathFinder<Tile>(graph);
|
||||
point.finder = new OptimizedPathFinder<>(graph);
|
||||
|
||||
point.path.clear();
|
||||
|
||||
point.pathTiles = null;
|
||||
|
||||
point.request = new PathFinderRequest<Tile>(point.start, Vars.control.getCore(), heuristic, point.path);
|
||||
point.request = new PathFinderRequest<>(point.start, Vars.control.getCore(), heuristic, point.path);
|
||||
point.request.statusChanged = true; //IMPORTANT!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**For an enemy that was just loaded from a save, find the node in the path it should be following.*/
|
||||
void findNode(Enemy enemy){
|
||||
if(enemy.lane >= Vars.control.getSpawnPoints().size){
|
||||
enemy.lane = 0;
|
||||
@ -183,16 +188,9 @@ public class Pathfind{
|
||||
}
|
||||
|
||||
enemy.node = closest;
|
||||
|
||||
//TODO
|
||||
|
||||
//Tile end = path[closest];
|
||||
//if the enemy can't get to this node, teleport to it
|
||||
//if(enemy.node < path.length - 2 && Vars.world.raycastWorld(enemy.x, enemy.y, end.worldx(), end.worldy()) != null){
|
||||
// Timers.run(Mathf.random(20f), () -> enemy.set(end.worldx(), end.worldy()));
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
/**Finds the closest tile to a position, in an array of tiles.*/
|
||||
private static int findClosest(Tile[] tiles, float x, float y){
|
||||
int cindex = -2;
|
||||
float dst = Float.MAX_VALUE;
|
||||
@ -209,28 +207,21 @@ public class Pathfind{
|
||||
|
||||
return cindex + 1;
|
||||
}
|
||||
|
||||
private static int indexOf(Tile tile, Tile[] tiles){
|
||||
int i = -1;
|
||||
for(int j = 0; j < tiles.length; j ++){
|
||||
if(tiles[j] == tile){
|
||||
return j;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
/**Returns whether a point is on a line.*/
|
||||
private static boolean onLine(Vector2 vector, float x1, float y1, float x2, float y2){
|
||||
return MathUtils.isEqual(vector.dst(x1, y1) + vector.dst(x2, y2), Vector2.dst(x1, y1, x2, y2), 0.01f);
|
||||
}
|
||||
|
||||
|
||||
/**Returns distance from a point to a line segment.*/
|
||||
private static float pointLineDist(float x, float y, float x2, float y2, float px, float py){
|
||||
float l2 = Vector2.dst2(x, y, x2, y2);
|
||||
float t = Math.max(0, Math.min(1, Vector2.dot(px - x, py - y, x2 - x, y2 - y) / l2));
|
||||
Vector2 projection = Tmp.v1.set(x, y).add(Tmp.v2.set(x2, y2).sub(x, y).scl(t)); // Projection falls on the segment
|
||||
return projection.dst(px, py);
|
||||
}
|
||||
|
||||
|
||||
//TODO documentation
|
||||
private static Vector2 projectPoint(float x1, float y1, float x2, float y2, float pointx, float pointy){
|
||||
float px = x2-x1, py = y2-y1, dAB = px*px + py*py;
|
||||
float u = ((pointx - x1) * px + (pointy - y1) * py) / dAB;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.Connection;
|
||||
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
public class TileConnection implements Connection<Tile>{
|
||||
@ -14,7 +13,7 @@ public class TileConnection implements Connection<Tile>{
|
||||
|
||||
@Override
|
||||
public float getCost(){
|
||||
return MHueristic.estimateStatic(a, b);
|
||||
return HueristicImpl.estimateStatic(a, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,15 +1,15 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.Connection;
|
||||
import com.badlogic.gdx.ai.pfa.indexed.IndexedGraph;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
/**Tilegraph that ignores player-made tiles.*/
|
||||
public class PassTileGraph implements IndexedGraph<Tile>{
|
||||
private Array<Connection<Tile>> tempConnections = new Array<Connection<Tile>>();
|
||||
|
||||
/**Tilegraph that ignores player-made tiles.*/
|
||||
public class TileGraph implements OptimizedGraph<Tile> {
|
||||
private Array<Connection<Tile>> tempConnections = new Array<Connection<Tile>>(4);
|
||||
|
||||
/**Used for the default Graph implementation. Returns a result similar to connectionsOf()*/
|
||||
@Override
|
||||
public Array<Connection<Tile>> getConnections(Tile fromNode){
|
||||
tempConnections.clear();
|
||||
@ -25,9 +25,21 @@ public class PassTileGraph implements IndexedGraph<Tile>{
|
||||
return tempConnections;
|
||||
}
|
||||
|
||||
/**Used for the OptimizedPathFinder implementation.*/
|
||||
@Override
|
||||
public Tile[] connectionsOf(Tile node){
|
||||
Tile[] nodes = node.getNearby();
|
||||
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.id();
|
||||
return node.packedPosition();
|
||||
}
|
||||
|
||||
@Override
|
@ -341,7 +341,7 @@ public class Control extends Module{
|
||||
Sounds.play("spawn");
|
||||
|
||||
if(lastUpdated < wave + 1){
|
||||
world.pathfinder().updatePath();
|
||||
world.pathfinder().resetPaths();
|
||||
lastUpdated = wave + 1;
|
||||
}
|
||||
|
||||
@ -652,7 +652,7 @@ public class Control extends Module{
|
||||
wavetime -= delta();
|
||||
|
||||
if(lastUpdated < wave + 1 && wavetime < Vars.aheadPathfinding){ //start updating beforehand
|
||||
world.pathfinder().updatePath();
|
||||
world.pathfinder().resetPaths();
|
||||
lastUpdated = wave + 1;
|
||||
}
|
||||
}else{
|
||||
|
@ -191,7 +191,7 @@ public class World extends Module{
|
||||
Vars.control.getTutorial().setDefaultBlocks(control.getCore().x, control.getCore().y);
|
||||
}
|
||||
|
||||
pathfind.updatePath();
|
||||
pathfind.resetPaths();
|
||||
}
|
||||
|
||||
void setDefaultBlocks(){
|
||||
|
@ -85,6 +85,12 @@ public class Generator{
|
||||
tiles[x][y].setFloor(floor);
|
||||
}
|
||||
}
|
||||
|
||||
for(int x = 0; x < pixmap.getWidth(); x ++){
|
||||
for(int y = 0; y < pixmap.getHeight(); y ++) {
|
||||
tiles[x][y].updateOcclusion();
|
||||
}
|
||||
}
|
||||
|
||||
if(!hascore){
|
||||
GameState.set(State.menu);
|
||||
|
@ -22,6 +22,8 @@ public class Tile{
|
||||
* This is relative to the block it is linked to; negate coords to find the link.*/
|
||||
public byte link = 0;
|
||||
public short x, y;
|
||||
/**Whether this tile has any solid blocks near it.*/
|
||||
public boolean occluded = false;
|
||||
public TileEntity entity;
|
||||
|
||||
public Tile(int x, int y){
|
||||
@ -218,7 +220,20 @@ public class Tile{
|
||||
}
|
||||
|
||||
public Tile[] getNearby(Tile[] copy){
|
||||
return Vars.world.getNearby(x, y);
|
||||
return Vars.world.getNearby(x, y, copy);
|
||||
}
|
||||
|
||||
public void updateOcclusion(){
|
||||
occluded = false;
|
||||
for(int dx = -1; dx <= 1; dx ++){
|
||||
for(int dy = -1; dy <= 1; dy ++){
|
||||
Tile tile = Vars.world.tile(x + dx, y + dy);
|
||||
if(tile != null && tile.solid()){
|
||||
occluded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void changed(){
|
||||
@ -232,6 +247,8 @@ public class Tile{
|
||||
if(block.destructible || block.update){
|
||||
entity = block.getEntity().init(this, block.update);
|
||||
}
|
||||
|
||||
updateOcclusion();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -22,7 +22,8 @@ public class BlockPart extends Block implements PowerAcceptor, LiquidAcceptor{
|
||||
|
||||
@Override
|
||||
public boolean isSolidFor(Tile tile){
|
||||
return tile.getLinked().solid() || tile.getLinked().block().isSolidFor(tile.getLinked());
|
||||
return tile.getLinked() == null
|
||||
|| (tile.getLinked().solid() || tile.getLinked().block().isSolidFor(tile.getLinked()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user