diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index f1eb8d8851..4745d57100 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -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 diff --git a/core/src/io/anuke/mindustry/game/Schematics.java b/core/src/io/anuke/mindustry/game/Schematics.java index 77e9345a27..be3ae0fa75 100644 --- a/core/src/io/anuke/mindustry/game/Schematics.java +++ b/core/src/io/anuke/mindustry/game/Schematics.java @@ -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; diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index a26f54c7db..40425933a5 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -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()){ diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index dec7f927fc..16ecdb2e47 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -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); diff --git a/core/src/io/anuke/mindustry/input/PlaceUtils.java b/core/src/io/anuke/mindustry/input/Placement.java similarity index 55% rename from core/src/io/anuke/mindustry/input/PlaceUtils.java rename to core/src/io/anuke/mindustry/input/Placement.java index fa21a9a021..13052d9a03 100644 --- a/core/src/io/anuke/mindustry/input/PlaceUtils.java +++ b/core/src/io/anuke/mindustry/input/Placement.java @@ -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 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 pathfindLine(int startX, int startY, int endX, int endY){ + public static Array 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 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 always 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); + } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java index 2f539b789d..a7372b6d06 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/SettingsMenuDialog.java @@ -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")){ diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index e52eda26cb..9710e1b8de 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -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. diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java index 77378a076d..d4200184fe 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conduit.java @@ -26,6 +26,7 @@ public class Conduit extends LiquidBlock implements Autotiler{ rotate = true; solid = false; floating = true; + conveyorPlacement = true; } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java index a5e847218d..68d9302511 100644 --- a/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java +++ b/core/src/io/anuke/mindustry/world/blocks/distribution/Conveyor.java @@ -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;