Auto-pathfinding diagonals

This commit is contained in:
Anuken 2019-11-02 14:09:16 -04:00
parent b8ea0f3aa6
commit 2209968963
9 changed files with 150 additions and 31 deletions

View file

@ -590,6 +590,7 @@ category.shooting = Shooting
category.optional = Optional Enhancements
setting.landscape.name = Lock Landscape
setting.shadows.name = Shadows
setting.blockreplace.name = Automatic Block Suggestions
setting.linear.name = Linear Filtering
setting.hints.name = Hints
setting.animatedwater.name = Animated Water

View file

@ -16,7 +16,7 @@ import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Schematic.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.input.PlaceUtils.*;
import io.anuke.mindustry.input.Placement.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
@ -258,7 +258,7 @@ public class Schematics implements Loadable{
/** Creates a schematic from a world selection. */
public Schematic create(int x, int y, int x2, int y2){
NormalizeResult result = PlaceUtils.normalizeArea(x, y, x2, y2, 0, false, maxSchematicSize);
NormalizeResult result = Placement.normalizeArea(x, y, x2, y2, 0, false, maxSchematicSize);
x = result.x;
y = result.y;
x2 = result.x2;

View file

@ -182,13 +182,14 @@ public class DesktopInput extends InputHandler{
selectScale = 0f;
}
rotation = Mathf.mod(rotation + (int)Core.input.axisTap(Binding.rotate), 4);
if(sreq != null){
sreq.rotation = Mathf.mod(sreq.rotation + (int)Core.input.axisTap(Binding.rotate), 4);
}
if(!Core.input.keyDown(Binding.zoom_hold) && Math.abs((int)Core.input.axisTap(Binding.rotate)) > 0){
rotation = Mathf.mod(rotation + (int)Core.input.axisTap(Binding.rotate), 4);
if(sreq != null){
sreq.rotation = Mathf.mod(sreq.rotation + (int)Core.input.axisTap(Binding.rotate), 4);
}
if(isPlacing() && mode == placing){
updateLine(selectX, selectY);
}else if(!selectRequests.isEmpty()){

View file

@ -25,7 +25,7 @@ import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.Teams.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.input.PlaceUtils.*;
import io.anuke.mindustry.input.Placement.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.fragments.*;
@ -368,8 +368,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
protected void drawBreakSelection(int x1, int y1, int x2, int y2){
NormalizeDrawResult result = PlaceUtils.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
NormalizeResult dresult = PlaceUtils.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
NormalizeResult dresult = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
for(int x = dresult.x; x <= dresult.x2; x++){
for(int y = dresult.y; y <= dresult.y2; y++){
@ -415,7 +415,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
protected void drawSelection(int x1, int y1, int x2, int y2, int maxLength){
NormalizeDrawResult result = PlaceUtils.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
Lines.stroke(2f);
@ -469,7 +469,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
/** Remove everything from the queue in a selection. */
protected void removeSelection(int x1, int y1, int x2, int y2, boolean flush){
NormalizeResult result = PlaceUtils.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
NormalizeResult result = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
for(int x = 0; x <= Math.abs(result.x2 - result.x); x++){
for(int y = 0; y <= Math.abs(result.y2 - result.y); y++){
int wx = x1 + x * Mathf.sign(x2 - x1);
@ -526,12 +526,14 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
lineRequests.add(req);
});
lineRequests.each(req -> {
Block replace = req.block.getReplacement(req, lineRequests);
if(replace.unlockedCur()){
req.block = replace;
}
});
if(Core.settings.getBool("blockreplace")){
lineRequests.each(req -> {
Block replace = req.block.getReplacement(req, lineRequests);
if(replace.unlockedCur()){
req.block = replace;
}
});
}
}
protected void updateLine(int x1, int y1){
@ -809,9 +811,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
if(diagonal){
points = PlaceUtils.pathfindLine(startX, startY, endX, endY);
points = Placement.pathfindLine(block != null && block.conveyorPlacement, startX, startY, endX, endY);
}else{
points = PlaceUtils.normalizeLine(startX, startY, endX, endY);
points = Placement.normalizeLine(startX, startY, endX, endY);
}
float angle = Angles.angle(startX, startY, endX, endY);

View file

@ -1,25 +1,40 @@
package io.anuke.mindustry.input;
import io.anuke.arc.collection.Array;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Bresenham2;
import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.util.pooling.Pools;
import io.anuke.mindustry.world.Block;
import io.anuke.arc.collection.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.world.*;
import static io.anuke.mindustry.Vars.tilesize;
import java.util.*;
public class PlaceUtils{
import static io.anuke.mindustry.Vars.*;
public class Placement{
private static final NormalizeResult result = new NormalizeResult();
private static final NormalizeDrawResult drawResult = new NormalizeDrawResult();
private static Bresenham2 bres = new Bresenham2();
private static Array<Point2> points = new Array<>();
//for pathfinding
private static IntFloatMap costs = new IntFloatMap();
private static IntIntMap parents = new IntIntMap();
private static IntSet closed = new IntSet();
/** Normalize a diagonal line into points. */
public static Array<Point2> pathfindLine(int startX, int startY, int endX, int endY){
public static Array<Point2> pathfindLine(boolean conveyors, int startX, int startY, int endX, int endY){
Pools.freeAll(points);
points.clear();
return bres.lineNoDiagonal(startX, startY, endX, endY, Pools.get(Point2.class, Point2::new), points);
if(conveyors){
if(astar(startX, startY, endX, endY)){
return points;
}else{
return normalizeLine(startX, startY, endX, endY);
}
}else{
return bres.lineNoDiagonal(startX, startY, endX, endY, Pools.get(Point2.class, Point2::new), points);
}
}
/** Normalize two points into one straight line, no diagonals. */
@ -40,6 +55,92 @@ public class PlaceUtils{
return points;
}
private static float tileHeuristic(Tile tile, Tile other){
Block block = control.input.block;
if(!other.block().alwaysReplace && !(block != null && block.canReplace(other.block()))){
return 20;
}else{
if(parents.containsKey(tile.pos())){
Tile prev = world.tile(parents.get(tile.pos(), 0));
if(tile.relativeTo(prev) != other.relativeTo(tile)){
return 8;
}
}
}
return 1;
}
private static float distanceHeuristic(int x1, int y1, int x2, int y2){
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
private static boolean validNode(Tile tile, Tile other){
Block block = control.input.block;
if(block != null && block.canReplace(other.block())){
return true;
}else{
return other.block().alwaysReplace;
}
}
private static boolean astar(int startX, int startY, int endX, int endY){
Tile start = world.tile(startX, startY);
Tile end = world.tile(endX, endY);
if(start == end || start == null || end == null) return false;
costs.clear();
closed.clear();
parents.clear();
int nodeLimit = 1000;
int totalNodes = 0;
PriorityQueue<Tile> queue = new PriorityQueue<>(10, (a, b) -> Float.compare(costs.get(a.pos(), 0f) + distanceHeuristic(a.x, a.y, end.x, end.y), costs.get(b.pos(), 0f) + distanceHeuristic(b.x, b.y, end.x, end.y)));
queue.add(start);
boolean found = false;
while(!queue.isEmpty() && totalNodes++ < nodeLimit){
Tile next = queue.poll();
float baseCost = costs.get(next.pos(), 0f);
if(next == end){
found = true;
break;
}
closed.add(Pos.get(next.x, next.y));
for(Point2 point : Geometry.d4){
int newx = next.x + point.x, newy = next.y + point.y;
Tile child = world.tile(newx, newy);
if(child != null && validNode(next, child)){
if(closed.add(child.pos())){
parents.put(child.pos(), next.pos());
costs.put(child.pos(), tileHeuristic(next, child) + baseCost);
queue.add(child);
}
}
}
}
if(!found) return false;
int total = 0;
points.add(Pools.obtain(Point2.class, Point2::new).set(endX, endY));
Tile current = end;
while(current != start && total++ < nodeLimit){
if(current == null) return false;
int newPos = parents.get(current.pos(), Pos.invalid);
if(newPos == Pos.invalid) return false;
points.add(Pools.obtain(Point2.class, Point2::new).set(Pos.x(newPos), Pos.y(newPos)));
current = world.tile(newPos);
}
points.reverse();
return true;
}
/**
* Normalizes a placement area and returns the result, ready to be used for drawing a rectangle.
* Returned x2 and y2 will <i>always</i> be greater than x and y.
@ -173,4 +274,12 @@ public class PlaceUtils{
return y + (x2 - x > y2 - y ? 0 : i);
}
}
public interface DistanceHeuristic{
float cost(int x1, int y1, int x2, int y2);
}
public interface TileHueristic{
float cost(Tile tile, Tile other);
}
}

View file

@ -226,6 +226,8 @@ public class SettingsMenuDialog extends SettingsDialog{
game.checkPref("savecreate", true);
game.checkPref("blockreplace", true);
game.checkPref("hints", true);
if(steam && !Version.modifier.contains("beta")){

View file

@ -91,6 +91,8 @@ public class Block extends BlockStorage{
public boolean consumesTap;
/** Whether the config is positional and needs to be shifted. */
public boolean posConfig;
/** Whether this block uses conveyor-type placement mode.*/
public boolean conveyorPlacement;
/**
* The color of this block when displayed on the minimap or map preview.
* Do not set manually! This is overriden when loading for most blocks.

View file

@ -26,6 +26,7 @@ public class Conduit extends LiquidBlock implements Autotiler{
rotate = true;
solid = false;
floating = true;
conveyorPlacement = true;
}
@Override

View file

@ -42,6 +42,7 @@ public class Conveyor extends Block implements Autotiler{
group = BlockGroup.transportation;
hasItems = true;
itemCapacity = 4;
conveyorPlacement = true;
idleSound = Sounds.conveyor;
idleSoundVolume = 0.004f;