diff --git a/core/src/io/anuke/mindustry/maps/generation/FortressGenerator.java b/core/src/io/anuke/mindustry/maps/generation/FortressGenerator.java index 0c59008089..988846cd43 100644 --- a/core/src/io/anuke/mindustry/maps/generation/FortressGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generation/FortressGenerator.java @@ -9,6 +9,7 @@ import io.anuke.mindustry.content.blocks.StorageBlocks; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.type.AmmoType; import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.Recipe; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Edges; import io.anuke.mindustry.world.Tile; @@ -44,7 +45,7 @@ public class FortressGenerator{ void gen(){ gen.setBlock(enemyX, enemyY, StorageBlocks.core, team); - float difficultyScl = Mathf.clamp(gen.sector.difficulty / 25f + Mathf.range(1f/2f), 0f, 0.9999f); + float difficultyScl = Mathf.clamp(gen.sector.difficulty / 20f + gen.random.range(1f/2f), 0f, 0.9999f); Array turrets = find(b -> (b instanceof ItemTurret && accepts(((ItemTurret) b).getAmmoTypes(), Items.copper) || b instanceof PowerTurret)); Array drills = find(b -> b instanceof Drill && !b.consumes.has(ConsumePower.class)); @@ -62,42 +63,49 @@ public class FortressGenerator{ } Item item = gen.drillItem(x, y, drill); + Block generator = gens.get(gen.random.random(0, gens.size-1)); - if(item != null && item == Items.copper && gen.canPlace(x, y, drill)){ + if(item != null && item == Items.copper && gen.canPlace(x, y, drill) && !gen.random.chance(0.5)){ gen.setBlock(x, y, drill, team); + }else if(gen.canPlace(x, y, generator) && gen.random.chance(0.01)){ + gen.setBlock(x, y, generator, team); } } } - Turret turret = (Turret) turrets.first(); + //Turret turret = (Turret) turrets.first(); //place down turrets for(int x = 0; x < gen.width; x++){ for(int y = 0; y < gen.height; y++){ - if(Vector2.dst(x, y, enemyX, enemyY) > coreDst + 4 || !gen.canPlace(x, y, turret)){ - continue; - } + for(Block block : turrets){ + Turret turret = (Turret)block; + if(Vector2.dst(x, y, enemyX, enemyY) > coreDst + 4 || !gen.canPlace(x, y, turret) || !gen.random.chance(0.5)){ + continue; + } - boolean found = false; + boolean found = false; - for(GridPoint2 point : Edges.getEdges(turret.size)){ - Tile tile = gen.tile(x + point.x, y + point.y); + for(GridPoint2 point : Edges.getEdges(turret.size)){ + Tile tile = gen.tile(x + point.x, y + point.y); - if(tile != null){ - tile = tile.target(); + if(tile != null){ + tile = tile.target(); - if(turret instanceof PowerTurret && tile.target().block() instanceof PowerGenerator){ - found = true; - break; - }else if(turret instanceof ItemTurret && tile.block() instanceof Drill && accepts(((ItemTurret) turret).getAmmoTypes(), gen.drillItem(tile.x, tile.y, (Drill) tile.block()))){ - found = true; - break; + if(turret instanceof PowerTurret && tile.target().block() instanceof PowerGenerator){ + found = true; + break; + }else if(turret instanceof ItemTurret && tile.block() instanceof Drill && accepts(((ItemTurret) turret).getAmmoTypes(), gen.drillItem(tile.x, tile.y, (Drill) tile.block()))){ + found = true; + break; + } } } - } - if(found){ - gen.setBlock(x, y, turret, team); + if(found){ + gen.setBlock(x, y, turret, team); + break; + } } } } @@ -125,7 +133,6 @@ public class FortressGenerator{ } } } - } boolean accepts(AmmoType[] types, Item item){ @@ -140,7 +147,7 @@ public class FortressGenerator{ Array find(Predicate pred){ Array out = new Array<>(); for(Block block : Block.all()){ - if(pred.evaluate(block)){ + if(pred.evaluate(block) && Recipe.getByResult(block) != null){ out.add(block); } } diff --git a/core/src/io/anuke/mindustry/maps/generation/pathfinding/AStarPathFinder.java b/core/src/io/anuke/mindustry/maps/generation/pathfinding/AStarPathFinder.java new file mode 100644 index 0000000000..912448908c --- /dev/null +++ b/core/src/io/anuke/mindustry/maps/generation/pathfinding/AStarPathFinder.java @@ -0,0 +1,264 @@ +package io.anuke.mindustry.maps.generation.pathfinding; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.BinaryHeap; +import com.badlogic.gdx.utils.IntMap; +import io.anuke.mindustry.content.fx.Fx; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.function.Predicate; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; + +//TODO +public class AStarPathFinder extends TilePathfinder{ + IntMap records = new IntMap<>(); + BinaryHeap openList; + NodeRecord current; + + private int searchId; + private Tile end; + + private static final byte UNVISITED = 0; + private static final byte OPEN = 1; + private static final byte CLOSED = 2; + + private static final boolean debug = false; + + public AStarPathFinder(Tile[][] tiles) { + super(tiles); + this.openList = new BinaryHeap<>(); + } + + @Override + public void search(Tile start, Predicate result, Array out){ + + } + + public boolean searchNodePath(Tile startNode, Tile endNode, Array outPath) { + this.end = endNode; + + // Perform AStar + boolean found = search(startNode, endNode); + + if (found) { + // Create a path made of nodes + generateNodePath(startNode, outPath); + } + + return found; + } + + protected boolean search(Tile startNode, Tile endNode) { + + initSearch(startNode, endNode); + + // 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); + + } while (openList.size > 0); + + // We've run out of nodes without finding the goal, so there's no solution + return false; + } +/* + public boolean search(PathFinderRequest 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.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); + + // 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(Tile startNode, Tile endNode) { + + // 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 startRecord = getNodeRecord(startNode); + startRecord.node = startNode; + //startRecord.connection = null; + startRecord.costSoFar = 0; + addToOpenList(startRecord, estimate(startNode, endNode)); + + current = null; + } + + protected void visitChildren(Tile endNode) { + if(debug) Effects.effect(Fx.spawn, current.node.worldx(), current.node.worldy()); + + nodes(current.node, node -> { + float addCost = estimate(current.node, node); + + float nodeCost = current.costSoFar + addCost; + + float nodeHeuristic; + NodeRecord nodeRecord = getNodeRecord(node); + + if (nodeRecord.category == CLOSED) { // The node is closed + + // If we didn't find a shorter route, skip + if (nodeRecord.costSoFar <= nodeCost){ + return; + } + + // 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){ + return; + } + + // 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 = estimate(node, endNode); + } + + // Update node record's cost and connection + nodeRecord.costSoFar = nodeCost; + nodeRecord.from = current.node; + + // Add it to the open list with the estimated total cost + addToOpenList(nodeRecord, nodeCost + nodeHeuristic); + }); + } + + protected void nodes(Tile current, Consumer cons){ + if(obstacle(current)) return; + for(int i = 0; i < 4; i ++){ + Tile n = current.getNearby(i); + if(!obstacle(n)) cons.accept(n); + } + } + + protected Tile rel(Tile tile, int i){ + return tile.getNearby(Geometry.d8[Mathf.mod(i, 8)]); + } + + protected boolean obstacle(Tile tile){ + return tile == null || (tile.solid() && end.target() != tile && tile.target() != end); + } + + protected float estimate(Tile tile, Tile other){ + return Math.abs(tile.worldx() - other.worldx()) + Math.abs(tile.worldy() - other.worldy()); + // (tile.occluded ? tilesize : 0) + (other.occluded ? tilesize : 0); + } + + protected void generateNodePath(Tile startNode, Array outPath) { + + // Work back along the path, accumulating nodes + // outPath.clear(); + while (current.from != null) { + outPath.add(current.node); + current = records.get(indexOf(current.from)); + } + outPath.add(startNode); + + // Reverse the path + outPath.reverse(); + } + + protected void addToOpenList(NodeRecord nodeRecord, float estimatedTotalCost) { + openList.add(nodeRecord, estimatedTotalCost); + nodeRecord.category = OPEN; + } + + protected NodeRecord getNodeRecord(Tile node) { + if(!records.containsKey(indexOf(node))){ + NodeRecord record = new NodeRecord(); + record.node = node; + record.searchId = searchId; + records.put(indexOf(node), record); + return record; + }else{ + NodeRecord record = records.get(indexOf(node)); + if(record.searchId != searchId){ + record.category = UNVISITED; + record.searchId = searchId; + } + return record; + } + } + + private int indexOf(Tile node){ + return node.packedPosition(); + } + + static class NodeRecord extends BinaryHeap.Node { + Tile node; + Tile from; + + float costSoFar; + byte category; + + int searchId; + + public NodeRecord() { + super(0); + } + + public float getEstimatedTotalCost() { + return getValue(); + } + } +} \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/maps/generation/pathfinding/FlowPathFinder.java b/core/src/io/anuke/mindustry/maps/generation/pathfinding/FlowPathFinder.java new file mode 100644 index 0000000000..b84377505a --- /dev/null +++ b/core/src/io/anuke/mindustry/maps/generation/pathfinding/FlowPathFinder.java @@ -0,0 +1,69 @@ +package io.anuke.mindustry.maps.generation.pathfinding; + +import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Queue; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.function.Predicate; +import io.anuke.ucore.util.Geometry; + +public class FlowPathFinder extends TilePathfinder{ + protected float[][] weights; + + public FlowPathFinder(Tile[][] tiles){ + super(tiles); + this.weights = new float[tiles.length][tiles[0].length]; + } + + public void search(Tile start, Predicate result, Array out){ + Queue queue = new Queue<>(); + + for(int i = 0; i < weights.length; i++){ + for(int j = 0; j < weights[0].length; j++){ + if(result.test(tiles[i][j])){ + weights[i][j] = Float.MAX_VALUE; + queue.addLast(tiles[i][j]); + }else{ + weights[i][j] = 0f; + } + } + } + + while(queue.size > 0){ + Tile tile = queue.first(); + for(GridPoint2 point : Geometry.d4){ + int nx = tile.x + point.x, ny = tile.y + point.y; + if(inBounds(nx, ny) && weights[nx][ny] < weights[tile.x][tile.y] && tiles[nx][ny].passable()){ + weights[nx][ny] = weights[tile.x][tile.y] - 1; + queue.addLast(tiles[nx][ny]); + } + } + } + + out.add(start); + while(true){ + Tile tile = out.peek(); + + Tile max = null; + float maxf = 0f; + for(GridPoint2 point : Geometry.d4){ + int nx = tile.x + point.x, ny = tile.y + point.y; + if(inBounds(nx, ny) && (weights[nx][ny] > maxf || max == null)){ + max = tiles[nx][ny]; + maxf = weights[nx][ny]; + + if(MathUtils.isEqual(maxf, Float.MAX_VALUE)){ + out.add(max); + return; + } + } + } + if(max == null){ + break; + } + out.add(max); + } + } + +} diff --git a/core/src/io/anuke/mindustry/maps/generation/pathfinding/TilePathfinder.java b/core/src/io/anuke/mindustry/maps/generation/pathfinding/TilePathfinder.java new file mode 100644 index 0000000000..98de454722 --- /dev/null +++ b/core/src/io/anuke/mindustry/maps/generation/pathfinding/TilePathfinder.java @@ -0,0 +1,20 @@ +package io.anuke.mindustry.maps.generation.pathfinding; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.function.Predicate; +import io.anuke.ucore.util.Mathf; + +public abstract class TilePathfinder{ + protected Tile[][] tiles; + + public TilePathfinder(Tile[][] tiles){ + this.tiles = tiles; + } + + protected boolean inBounds(int x, int y){ + return Mathf.inBounds(x, y, tiles); + } + + public abstract void search(Tile start, Predicate result, Array out); +}