diff --git a/android/src/io/anuke/mindustry/AndroidLauncher.java b/android/src/io/anuke/mindustry/AndroidLauncher.java index 72a73e5156..beb54052be 100644 --- a/android/src/io/anuke/mindustry/AndroidLauncher.java +++ b/android/src/io/anuke/mindustry/AndroidLauncher.java @@ -104,7 +104,7 @@ public class AndroidLauncher extends AndroidApplication{ @Override public boolean isDebug() { - return false; + return true; } @Override diff --git a/core/src/io/anuke/mindustry/ai/Pathfinder.java b/core/src/io/anuke/mindustry/ai/Pathfinder.java index a3f3f0cfda..0d7ba24f9c 100644 --- a/core/src/io/anuke/mindustry/ai/Pathfinder.java +++ b/core/src/io/anuke/mindustry/ai/Pathfinder.java @@ -2,8 +2,9 @@ package io.anuke.mindustry.ai; import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.utils.IntArray; +import com.badlogic.gdx.utils.ObjectSet; 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.WorldLoadEvent; import io.anuke.mindustry.game.Team; @@ -19,22 +20,23 @@ import static io.anuke.mindustry.Vars.state; import static io.anuke.mindustry.Vars.world; public class Pathfinder { - private static final float unitBlockCost = 4f; - - private AsyncExecutor executor = new AsyncExecutor(8); - private float[][][] weights; + private long maxUpdate = TimeUtils.millisToNanos(5); + private PathData[] paths; private IntArray blocked = new IntArray(); public Pathfinder(){ 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){ - float[][] values = weights[team.ordinal()]; + float[][] values = paths[team.ordinal()].weights; if(values == null) return tile; @@ -57,68 +59,114 @@ public class Pathfinder { if(target == null || tl == Float.MAX_VALUE) return tile; - return target; } 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){ - return (tile.getWallID() == 0 && !(tile.floor().liquid && (tile.floor().damageTaken > 0 || tile.floor().drownTime > 0))) || tile.breakable(); + private boolean passable(Tile tile, Team team){ + 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 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(){ Timers.mark(); - weights = new float[Team.values().length][0][0]; + paths = new PathData[Team.values().length]; blocked.clear(); for(TeamData data : state.teams.getTeams()){ - float[][] values = new float[world.width()][world.height()]; - weights[data.team.ordinal()] = values; + PathData path = new PathData(); + paths[data.team.ordinal()] = path; - Queue frontier = new Queue<>(); - 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; - } - } - } - } + createFor(data.team); } Log.info("Elapsed calculation time: {0}", Timers.elapsed()); } + + class PathData{ + float[][] weights; + int[][] searches; + int search = 0; + Queue frontier = new Queue<>(); + + PathData(){ + weights = new float[world.width()][world.height()]; + searches = new int[world.width()][world.height()]; + } + } } diff --git a/core/src/io/anuke/mindustry/core/Logic.java b/core/src/io/anuke/mindustry/core/Logic.java index c9ea33b3e6..2918be163b 100644 --- a/core/src/io/anuke/mindustry/core/Logic.java +++ b/core/src/io/anuke/mindustry/core/Logic.java @@ -142,6 +142,8 @@ public class Logic extends Module { } Entities.collideGroups(bulletGroup, playerGroup); + + world.pathfinder().update(); } } } diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index 724e773dec..9b640b88b4 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -241,7 +241,7 @@ public class Renderer extends RendererModule{ if(pixelate) Graphics.flushSurface(); - //drawDebug(); + drawDebug(); drawPlayerNames(); batch.end(); @@ -308,7 +308,7 @@ public class Renderer extends RendererModule{ } 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); for(int x = -rangex; x <= rangex; x++) { diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index 49e684a3fc..8ff5a7bd71 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -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){ 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()){ @@ -182,13 +182,14 @@ public abstract class InputHandler extends InputAdapter{ if(!Net.client()){ 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){ - if(!Net.client()) - Placement.breakBlock(player.team, x, y, true, sound); + if(!Net.client()){ + threads.run(() -> Placement.breakBlock(player.team, x, y, true, sound)); + } if(Net.active()){ NetEvents.handleBreak(x, y); diff --git a/core/src/io/anuke/mindustry/world/BlockFlag.java b/core/src/io/anuke/mindustry/world/BlockFlag.java index 76738f2d1e..11a5716c9f 100644 --- a/core/src/io/anuke/mindustry/world/BlockFlag.java +++ b/core/src/io/anuke/mindustry/world/BlockFlag.java @@ -1,7 +1,8 @@ package io.anuke.mindustry.world; public enum BlockFlag { - resupplyPoint(0), + target(0), + resupplyPoint(Float.MAX_VALUE), producer(Float.MAX_VALUE), repair(Float.MAX_VALUE); diff --git a/core/src/io/anuke/mindustry/world/blocks/types/storage/CoreBlock.java b/core/src/io/anuke/mindustry/world/blocks/types/storage/CoreBlock.java index 6177ce0e40..934738c3c0 100644 --- a/core/src/io/anuke/mindustry/world/blocks/types/storage/CoreBlock.java +++ b/core/src/io/anuke/mindustry/world/blocks/types/storage/CoreBlock.java @@ -29,7 +29,7 @@ public class CoreBlock extends StorageBlock { size = 3; hasItems = true; itemCapacity = 2000; - flags = EnumSet.of(BlockFlag.resupplyPoint); + flags = EnumSet.of(BlockFlag.resupplyPoint, BlockFlag.target); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/types/units/ResupplyPoint.java b/core/src/io/anuke/mindustry/world/blocks/types/units/ResupplyPoint.java index ae208b4aaa..eb90d5cb22 100644 --- a/core/src/io/anuke/mindustry/world/blocks/types/units/ResupplyPoint.java +++ b/core/src/io/anuke/mindustry/world/blocks/types/units/ResupplyPoint.java @@ -32,7 +32,7 @@ public class ResupplyPoint extends Block{ super(name); update = true; solid = true; - flags = EnumSet.of(BlockFlag.resupplyPoint); + flags = EnumSet.of(BlockFlag.resupplyPoint, BlockFlag.target); layer = Layer.laser; hasItems = true; hasPower = true;