Implemented auto-updating pathfinder
This commit is contained in:
@@ -104,7 +104,7 @@ public class AndroidLauncher extends AndroidApplication{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDebug() {
|
public boolean isDebug() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package io.anuke.mindustry.ai;
|
|||||||
|
|
||||||
import com.badlogic.gdx.math.GridPoint2;
|
import com.badlogic.gdx.math.GridPoint2;
|
||||||
import com.badlogic.gdx.utils.IntArray;
|
import com.badlogic.gdx.utils.IntArray;
|
||||||
|
import com.badlogic.gdx.utils.ObjectSet;
|
||||||
import com.badlogic.gdx.utils.Queue;
|
import com.badlogic.gdx.utils.Queue;
|
||||||
import com.badlogic.gdx.utils.async.AsyncExecutor;
|
import com.badlogic.gdx.utils.TimeUtils;
|
||||||
import io.anuke.mindustry.game.EventType.TileChangeEvent;
|
import io.anuke.mindustry.game.EventType.TileChangeEvent;
|
||||||
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
|
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
|
||||||
import io.anuke.mindustry.game.Team;
|
import io.anuke.mindustry.game.Team;
|
||||||
@@ -19,22 +20,23 @@ import static io.anuke.mindustry.Vars.state;
|
|||||||
import static io.anuke.mindustry.Vars.world;
|
import static io.anuke.mindustry.Vars.world;
|
||||||
|
|
||||||
public class Pathfinder {
|
public class Pathfinder {
|
||||||
private static final float unitBlockCost = 4f;
|
private long maxUpdate = TimeUtils.millisToNanos(5);
|
||||||
|
private PathData[] paths;
|
||||||
private AsyncExecutor executor = new AsyncExecutor(8);
|
|
||||||
private float[][][] weights;
|
|
||||||
private IntArray blocked = new IntArray();
|
private IntArray blocked = new IntArray();
|
||||||
|
|
||||||
public Pathfinder(){
|
public Pathfinder(){
|
||||||
Events.on(WorldLoadEvent.class, this::clear);
|
Events.on(WorldLoadEvent.class, this::clear);
|
||||||
|
Events.on(TileChangeEvent.class, this::update);
|
||||||
|
}
|
||||||
|
|
||||||
Events.on(TileChangeEvent.class, tile -> {
|
public void update(){
|
||||||
|
for(TeamData team : state.teams.getTeams()){
|
||||||
});
|
updateFrontier(team.team, maxUpdate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tile getTargetTile(Team team, Tile tile){
|
public Tile getTargetTile(Team team, Tile tile){
|
||||||
float[][] values = weights[team.ordinal()];
|
float[][] values = paths[team.ordinal()].weights;
|
||||||
|
|
||||||
if(values == null) return tile;
|
if(values == null) return tile;
|
||||||
|
|
||||||
@@ -57,68 +59,114 @@ public class Pathfinder {
|
|||||||
|
|
||||||
if(target == null || tl == Float.MAX_VALUE) return tile;
|
if(target == null || tl == Float.MAX_VALUE) return tile;
|
||||||
|
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getDebugValue(int x, int y){
|
public float getDebugValue(int x, int y){
|
||||||
return weights[Team.red.ordinal()][x][y];
|
return paths[Team.red.ordinal()].weights[x][y];
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean passable(Tile tile){
|
private boolean passable(Tile tile, Team team){
|
||||||
return (tile.getWallID() == 0 && !(tile.floor().liquid && (tile.floor().damageTaken > 0 || tile.floor().drownTime > 0))) || tile.breakable();
|
return (tile.getWallID() == 0 && !(tile.floor().liquid && (tile.floor().damageTaken > 0 || tile.floor().drownTime > 0))) || (tile.breakable()
|
||||||
|
&& tile.getTeam() != team);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(Tile tile){
|
||||||
|
if(paths[tile.getTeam().ordinal()] != null) {
|
||||||
|
PathData path = paths[tile.getTeam().ordinal()];
|
||||||
|
|
||||||
|
if(!passable(tile, tile.getTeam())){
|
||||||
|
path.weights[tile.x][tile.y] = Float.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.search ++;
|
||||||
|
path.frontier.clear();
|
||||||
|
|
||||||
|
ObjectSet<Tile> set = world.indexer().getEnemy(tile.getTeam(), BlockFlag.target);
|
||||||
|
for(Tile other : set){
|
||||||
|
path.weights[other.x][other.y] = 0;
|
||||||
|
path.searches[other.x][other.y] = path.search;
|
||||||
|
path.frontier.addFirst(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createFor(Team team){
|
||||||
|
PathData path = new PathData();
|
||||||
|
path.search ++;
|
||||||
|
path.frontier.ensureCapacity(world.width() * world.height() / 2);
|
||||||
|
|
||||||
|
paths[team.ordinal()] = path;
|
||||||
|
|
||||||
|
for (int x = 0; x < world.width(); x++) {
|
||||||
|
for (int y = 0; y < world.height(); y++) {
|
||||||
|
Tile tile = world.tile(x, y);
|
||||||
|
|
||||||
|
if (tile.block().flags != null && state.teams.areEnemies(tile.getTeam(), team)
|
||||||
|
&& tile.block().flags.contains(BlockFlag.target)) {
|
||||||
|
path.frontier.addFirst(tile);
|
||||||
|
path.weights[x][y] = 0;
|
||||||
|
path.searches[x][y] = path.search;
|
||||||
|
}else{
|
||||||
|
path.weights[x][y] = Float.MAX_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFrontier(team, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFrontier(Team team, long nsToRun){
|
||||||
|
PathData path = paths[team.ordinal()];
|
||||||
|
|
||||||
|
long start = TimeUtils.nanoTime();
|
||||||
|
|
||||||
|
while (path.frontier.size > 0 && (nsToRun < 0 || TimeUtils.timeSinceNanos(start) <= nsToRun)) {
|
||||||
|
Tile tile = path.frontier.removeLast();
|
||||||
|
float cost = path.weights[tile.x][tile.y];
|
||||||
|
|
||||||
|
if (cost < Float.MAX_VALUE) {
|
||||||
|
for (GridPoint2 point : Geometry.d4) {
|
||||||
|
|
||||||
|
int dx = tile.x + point.x, dy = tile.y + point.y;
|
||||||
|
Tile other = world.tile(dx, dy);
|
||||||
|
|
||||||
|
if (other != null && (path.weights[dx][dy] > cost + 1 || path.searches[dx][dy] < path.search)
|
||||||
|
&& passable(other, team)){
|
||||||
|
path.frontier.addFirst(world.tile(dx, dy));
|
||||||
|
path.weights[dx][dy] = cost + other.cost;
|
||||||
|
path.searches[dx][dy] = path.search;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clear(){
|
private void clear(){
|
||||||
Timers.mark();
|
Timers.mark();
|
||||||
|
|
||||||
weights = new float[Team.values().length][0][0];
|
paths = new PathData[Team.values().length];
|
||||||
blocked.clear();
|
blocked.clear();
|
||||||
|
|
||||||
for(TeamData data : state.teams.getTeams()){
|
for(TeamData data : state.teams.getTeams()){
|
||||||
float[][] values = new float[world.width()][world.height()];
|
PathData path = new PathData();
|
||||||
weights[data.team.ordinal()] = values;
|
paths[data.team.ordinal()] = path;
|
||||||
|
|
||||||
Queue<Tile> frontier = new Queue<>();
|
createFor(data.team);
|
||||||
frontier.ensureCapacity(world.width() * world.height() / 2);
|
|
||||||
|
|
||||||
for (int x = 0; x < world.width(); x++) {
|
|
||||||
for (int y = 0; y < world.height(); y++) {
|
|
||||||
Tile tile = world.tile(x, y);
|
|
||||||
float min = Float.MAX_VALUE;
|
|
||||||
|
|
||||||
if (tile.block().flags != null && state.teams.areEnemies(tile.getTeam(), data.team)) {
|
|
||||||
for (BlockFlag flag : tile.block().flags) {
|
|
||||||
min = Math.min(flag.cost, min);
|
|
||||||
}
|
|
||||||
|
|
||||||
frontier.addFirst(tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
values[x][y] = min;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (frontier.size > 0) {
|
|
||||||
Tile tile = frontier.removeLast();
|
|
||||||
float cost = values[tile.x][tile.y];
|
|
||||||
|
|
||||||
if (cost < Float.MAX_VALUE) {
|
|
||||||
for (GridPoint2 point : Geometry.d4) {
|
|
||||||
|
|
||||||
int dx = tile.x + point.x, dy = tile.y + point.y;
|
|
||||||
Tile other = world.tile(dx, dy);
|
|
||||||
|
|
||||||
if (other != null && values[dx][dy] > cost + 1 && passable(other)) {
|
|
||||||
|
|
||||||
frontier.addFirst(world.tile(dx, dy));
|
|
||||||
values[dx][dy] = cost + other.cost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.info("Elapsed calculation time: {0}", Timers.elapsed());
|
Log.info("Elapsed calculation time: {0}", Timers.elapsed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PathData{
|
||||||
|
float[][] weights;
|
||||||
|
int[][] searches;
|
||||||
|
int search = 0;
|
||||||
|
Queue<Tile> frontier = new Queue<>();
|
||||||
|
|
||||||
|
PathData(){
|
||||||
|
weights = new float[world.width()][world.height()];
|
||||||
|
searches = new int[world.width()][world.height()];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ public class Logic extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Entities.collideGroups(bulletGroup, playerGroup);
|
Entities.collideGroups(bulletGroup, playerGroup);
|
||||||
|
|
||||||
|
world.pathfinder().update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ public class Renderer extends RendererModule{
|
|||||||
if(pixelate)
|
if(pixelate)
|
||||||
Graphics.flushSurface();
|
Graphics.flushSurface();
|
||||||
|
|
||||||
//drawDebug();
|
drawDebug();
|
||||||
drawPlayerNames();
|
drawPlayerNames();
|
||||||
|
|
||||||
batch.end();
|
batch.end();
|
||||||
@@ -308,7 +308,7 @@ public class Renderer extends RendererModule{
|
|||||||
}
|
}
|
||||||
|
|
||||||
void drawDebug(){
|
void drawDebug(){
|
||||||
int rangex = 50, rangey = 50;
|
int rangex = (int)(Core.camera.viewportWidth/tilesize/2), rangey = (int)(Core.camera.viewportHeight/tilesize/2);
|
||||||
Draw.tscl(0.125f);
|
Draw.tscl(0.125f);
|
||||||
|
|
||||||
for(int x = -rangex; x <= rangex; x++) {
|
for(int x = -rangex; x <= rangex; x++) {
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ public abstract class InputHandler extends InputAdapter{
|
|||||||
|
|
||||||
public void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){
|
public void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){
|
||||||
if(!Net.client()){ //is server or singleplayer
|
if(!Net.client()){ //is server or singleplayer
|
||||||
Placement.placeBlock(player.team, x, y, result, rotation, effects, sound);
|
threads.run(() -> Placement.placeBlock(player.team, x, y, result, rotation, effects, sound));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Net.active()){
|
if(Net.active()){
|
||||||
@@ -182,13 +182,14 @@ public abstract class InputHandler extends InputAdapter{
|
|||||||
|
|
||||||
if(!Net.client()){
|
if(!Net.client()){
|
||||||
Tile tile = world.tile(x, y);
|
Tile tile = world.tile(x, y);
|
||||||
if(tile != null) result.placed(tile);
|
if(tile != null) threads.run(() -> result.placed(tile));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void breakBlock(int x, int y, boolean sound){
|
public void breakBlock(int x, int y, boolean sound){
|
||||||
if(!Net.client())
|
if(!Net.client()){
|
||||||
Placement.breakBlock(player.team, x, y, true, sound);
|
threads.run(() -> Placement.breakBlock(player.team, x, y, true, sound));
|
||||||
|
}
|
||||||
|
|
||||||
if(Net.active()){
|
if(Net.active()){
|
||||||
NetEvents.handleBreak(x, y);
|
NetEvents.handleBreak(x, y);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package io.anuke.mindustry.world;
|
package io.anuke.mindustry.world;
|
||||||
|
|
||||||
public enum BlockFlag {
|
public enum BlockFlag {
|
||||||
resupplyPoint(0),
|
target(0),
|
||||||
|
resupplyPoint(Float.MAX_VALUE),
|
||||||
producer(Float.MAX_VALUE),
|
producer(Float.MAX_VALUE),
|
||||||
repair(Float.MAX_VALUE);
|
repair(Float.MAX_VALUE);
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class CoreBlock extends StorageBlock {
|
|||||||
size = 3;
|
size = 3;
|
||||||
hasItems = true;
|
hasItems = true;
|
||||||
itemCapacity = 2000;
|
itemCapacity = 2000;
|
||||||
flags = EnumSet.of(BlockFlag.resupplyPoint);
|
flags = EnumSet.of(BlockFlag.resupplyPoint, BlockFlag.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class ResupplyPoint extends Block{
|
|||||||
super(name);
|
super(name);
|
||||||
update = true;
|
update = true;
|
||||||
solid = true;
|
solid = true;
|
||||||
flags = EnumSet.of(BlockFlag.resupplyPoint);
|
flags = EnumSet.of(BlockFlag.resupplyPoint, BlockFlag.target);
|
||||||
layer = Layer.laser;
|
layer = Layer.laser;
|
||||||
hasItems = true;
|
hasItems = true;
|
||||||
hasPower = true;
|
hasPower = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user