Implemented JPS badly
This commit is contained in:
@@ -19,6 +19,7 @@ import io.anuke.mindustry.net.ServerDebug;
|
||||
import io.anuke.ucore.UCore;
|
||||
import io.anuke.ucore.entities.EffectEntity;
|
||||
import io.anuke.ucore.entities.Entities;
|
||||
import io.anuke.ucore.entities.Entity;
|
||||
import io.anuke.ucore.entities.EntityGroup;
|
||||
import io.anuke.ucore.scene.ui.layout.Unit;
|
||||
|
||||
@@ -146,7 +147,7 @@ public class Vars{
|
||||
public static final EntityGroup<Bullet> bulletGroup = Entities.addGroup(Bullet.class);
|
||||
public static final EntityGroup<Shield> shieldGroup = Entities.addGroup(Shield.class, false);
|
||||
public static final EntityGroup<EffectEntity> effectGroup = Entities.addGroup(EffectEntity.class, false);
|
||||
public static final EntityGroup<EffectEntity> groundEffectGroup = Entities.addGroup(EffectEntity.class, false);
|
||||
public static final EntityGroup<Entity> groundEffectGroup = Entities.addGroup(Entity.class, false);
|
||||
public static final EntityGroup<Puddle> puddleGroup = Entities.addGroup(Puddle.class, false);
|
||||
public static final EntityGroup<Fire> airItemGroup = Entities.addGroup(Fire.class, false);
|
||||
public static final EntityGroup<BaseUnit>[] unitGroups = new EntityGroup[Team.values().length];
|
||||
|
||||
@@ -1,42 +1,43 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.*;
|
||||
import com.badlogic.gdx.ai.pfa.GraphPath;
|
||||
import com.badlogic.gdx.ai.pfa.PathFinderQueue;
|
||||
import com.badlogic.gdx.ai.pfa.PathFinderRequest;
|
||||
import com.badlogic.gdx.utils.BinaryHeap;
|
||||
import com.badlogic.gdx.utils.IntMap;
|
||||
import com.badlogic.gdx.utils.TimeUtils;
|
||||
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.util.Geometry;
|
||||
import io.anuke.ucore.util.Mathf;
|
||||
|
||||
/**An IndexedAStarPathfinder that uses an OptimizedGraph, and therefore has less allocations.*/
|
||||
public class OptimizedPathFinder<N> implements PathFinder<N> {
|
||||
OptimizedGraph<N> graph;
|
||||
IntMap<NodeRecord<N>> records = new IntMap<>();
|
||||
BinaryHeap<NodeRecord<N>> openList;
|
||||
NodeRecord<N> current;
|
||||
public class OptimizedPathFinder {
|
||||
IntMap<NodeRecord> records = new IntMap<>();
|
||||
BinaryHeap<NodeRecord> openList;
|
||||
NodeRecord current;
|
||||
|
||||
/**
|
||||
* The unique ID for each search run. Used to mark nodes.
|
||||
*/
|
||||
private int searchId;
|
||||
private Tile cameFrom = null;
|
||||
|
||||
private static final byte UNVISITED = 0;
|
||||
private static final byte OPEN = 1;
|
||||
private static final byte CLOSED = 2;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public OptimizedPathFinder(OptimizedGraph<N> graph) {
|
||||
this.graph = graph;
|
||||
private static final boolean debug = true;
|
||||
|
||||
public static boolean unop = false;
|
||||
|
||||
public OptimizedPathFinder() {
|
||||
this.openList = new BinaryHeap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean searchConnectionPath(N startNode, N endNode, Heuristic<N> heuristic, GraphPath<Connection<N>> outPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean searchNodePath(N startNode, N endNode, Heuristic<N> heuristic, GraphPath<N> outPath) {
|
||||
public boolean searchNodePath(Tile startNode, Tile endNode, GraphPath<Tile> outPath) {
|
||||
|
||||
// Perform AStar
|
||||
boolean found = search(startNode, endNode, heuristic);
|
||||
boolean found = search(startNode, endNode);
|
||||
|
||||
if (found) {
|
||||
// Create a path made of nodes
|
||||
@@ -46,9 +47,9 @@ public class OptimizedPathFinder<N> implements PathFinder<N> {
|
||||
return found;
|
||||
}
|
||||
|
||||
protected boolean search(N startNode, N endNode, Heuristic<N> heuristic) {
|
||||
protected boolean search(Tile startNode, Tile endNode) {
|
||||
|
||||
initSearch(startNode, endNode, heuristic);
|
||||
initSearch(startNode, endNode);
|
||||
|
||||
// Iterate through processing each node
|
||||
do {
|
||||
@@ -59,7 +60,9 @@ public class OptimizedPathFinder<N> implements PathFinder<N> {
|
||||
// Terminate if we reached the goal node
|
||||
if (current.node == endNode) return true;
|
||||
|
||||
visitChildren(endNode, heuristic);
|
||||
visitChildren(endNode);
|
||||
|
||||
cameFrom = current.node;
|
||||
|
||||
} while (openList.size > 0);
|
||||
|
||||
@@ -67,14 +70,13 @@ public class OptimizedPathFinder<N> implements PathFinder<N> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean search(PathFinderRequest<N> request, long timeToRun) {
|
||||
public boolean search(PathFinderRequest<Tile> 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.heuristic);
|
||||
initSearch(request.startNode, request.endNode);
|
||||
request.statusChanged = false;
|
||||
}
|
||||
|
||||
@@ -100,7 +102,7 @@ public class OptimizedPathFinder<N> implements PathFinder<N> {
|
||||
}
|
||||
|
||||
// Visit current node's children
|
||||
visitChildren(request.endNode, request.heuristic);
|
||||
visitChildren(request.endNode);
|
||||
|
||||
// Store the current time
|
||||
lastTime = currentTime;
|
||||
@@ -112,57 +114,52 @@ public class OptimizedPathFinder<N> implements PathFinder<N> {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void initSearch(N startNode, N endNode, Heuristic<N> heuristic) {
|
||||
protected void initSearch(Tile startNode, Tile endNode) {
|
||||
|
||||
// Increment the search id
|
||||
if (++searchId < 0) searchId = 1;
|
||||
|
||||
// Initialize the open list
|
||||
openList.clear();
|
||||
cameFrom = null;
|
||||
|
||||
// Initialize the record for the start node and add it to the open list
|
||||
NodeRecord<N> startRecord = getNodeRecord(startNode);
|
||||
NodeRecord startRecord = getNodeRecord(startNode);
|
||||
startRecord.node = startNode;
|
||||
//startRecord.connection = null;
|
||||
startRecord.costSoFar = 0;
|
||||
addToOpenList(startRecord, heuristic.estimate(startNode, endNode));
|
||||
addToOpenList(startRecord, estimate(startNode, endNode));
|
||||
|
||||
current = null;
|
||||
}
|
||||
|
||||
protected void visitChildren(N endNode, Heuristic<N> heuristic) {
|
||||
// Get current node's outgoing connections
|
||||
//Array<Connection<N>> connections = graph.getConnections(current.node);
|
||||
N[] conn = graph.connectionsOf(current.node);
|
||||
protected void visitChildren(Tile endNode) {
|
||||
if(debug) Effects.effect(Fx.node3, current.node.worldx(), current.node.worldy());
|
||||
|
||||
// Loop through each connection in turn
|
||||
for (int i = 0; i < conn.length; i++) {
|
||||
|
||||
//Connection<N> connection = connections.get(i)
|
||||
|
||||
// Get the cost estimate for the node
|
||||
N node = conn[i];
|
||||
|
||||
if(node == null) continue;
|
||||
|
||||
float addCost = heuristic.estimate(current.node, node);
|
||||
jps(current.node, current.from == null ? -1 : relDirection(current.node, current.from), endNode, node -> {
|
||||
float addCost = estimate(current.node, node);
|
||||
|
||||
float nodeCost = current.costSoFar + addCost;
|
||||
|
||||
float nodeHeuristic;
|
||||
NodeRecord<N> nodeRecord = getNodeRecord(node);
|
||||
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) continue;
|
||||
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) continue;
|
||||
//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);
|
||||
@@ -174,26 +171,162 @@ public class OptimizedPathFinder<N> implements PathFinder<N> {
|
||||
|
||||
// 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 = heuristic.estimate(node, endNode);
|
||||
nodeHeuristic = estimate(node, endNode);
|
||||
}
|
||||
|
||||
// Update node record's cost and connection
|
||||
nodeRecord.costSoFar = nodeCost;
|
||||
nodeRecord.from = current.node; //TODO ???
|
||||
nodeRecord.from = current.node;
|
||||
|
||||
// Add it to the open list with the estimated total cost
|
||||
addToOpenList(nodeRecord, nodeCost + nodeHeuristic);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
protected void generateNodePath(N startNode, GraphPath<N> outPath) {
|
||||
|
||||
protected void jps(Tile current, int direction, Tile end, Consumer<Tile> cons){
|
||||
if(obstacle(current)) return; //skip solid or off-the-screen stuff
|
||||
|
||||
//Log.info("jps {0} {1} // {2}", current.x, current.y, direction);
|
||||
|
||||
if(unop){
|
||||
for(int i = 0; i < 4; i ++){
|
||||
if(!obstacle(current.getNearby(i))) cons.accept(current.getNearby(i));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//if there's no start point, scan everything.
|
||||
if(direction == -1){
|
||||
for(int i = 0; i < 8; i ++){
|
||||
jps(current.getNearby(Geometry.d8[i]), i, end, cons);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(direction % 2 == 0){
|
||||
//forced neighbor in the straight pattern
|
||||
if(obstacle(rel(current, direction + 2)) && !obstacle(rel(current, direction + 1))){
|
||||
cons.accept(rel(current, direction + 1));
|
||||
}
|
||||
|
||||
if(obstacle(rel(current, direction - 2)) && !obstacle(rel(current, direction - 1))){
|
||||
cons.accept(rel(current, direction - 1));
|
||||
}
|
||||
}else{ //moving diagonal
|
||||
//forced neighbor in the diagonal pattern
|
||||
if(obstacle(rel(current, direction + 3)) && !obstacle(rel(current, direction + 2)) && !obstacle(rel(current, direction -2))) {
|
||||
cons.accept(rel(current, direction + 2));//jps(rel(current, direction + 2), Mathf.mod(direction + 2, 8), end, cons);
|
||||
}
|
||||
|
||||
if(obstacle(rel(current, direction - 3)) && !obstacle(rel(current, direction - 2))&& !obstacle(rel(current, direction + 2))){
|
||||
cons.accept(rel(current, direction - 2));//jps(rel(current, direction - 2), Mathf.mod(direction - 2, 8), end, cons);
|
||||
}
|
||||
}
|
||||
|
||||
while(!obstacle(current) && !trap(current, direction)){
|
||||
if(debug) Effects.effect(Fx.node1, current.worldx(), current.worldy());
|
||||
//moving straight
|
||||
if(direction % 2 == 0){
|
||||
Tile sf = scanDir(rel(current, direction), end, direction); //check if there's anything of interest going straight
|
||||
if(sf != null){ //if there is, jump to that location immediately and stop
|
||||
cons.accept(sf);
|
||||
return;
|
||||
}
|
||||
}else{ //moving diagonal
|
||||
Tile sf = scanDir(rel(current, direction), end, direction);
|
||||
|
||||
if(sf != null){
|
||||
cons.accept(sf);
|
||||
return;
|
||||
}
|
||||
|
||||
Tile sl = scanDir(rel(current, Mathf.mod(direction - 1, 8)), end, Mathf.mod(direction - 1, 8));
|
||||
|
||||
if(sl != null){
|
||||
cons.accept(sl);
|
||||
}
|
||||
|
||||
Tile sr = scanDir(rel(current, Mathf.mod(direction + 1, 8)), end, Mathf.mod(direction + 1, 8));
|
||||
|
||||
if(sr != null){
|
||||
cons.accept(sr);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if(current == end){
|
||||
cons.accept(end);
|
||||
return;
|
||||
}
|
||||
|
||||
current = rel(current, direction);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean trap(Tile tile, int direction){
|
||||
return direction % 2 == 1 && obstacle(rel(tile, direction - 1)) && obstacle(rel(tile, direction + 1));
|
||||
}
|
||||
|
||||
protected Tile scanDir(Tile tile, Tile end, int direction){
|
||||
while(!obstacle(tile)){
|
||||
if(debug) Effects.effect(Fx.node2, tile.worldx(), tile.worldy());
|
||||
if(tile == end) return tile;
|
||||
if(direction % 2 == 0){
|
||||
//forced neighbor in the straight pattern
|
||||
if((obstacle(rel(tile, direction + 2)) && !obstacle(rel(tile, direction + 1))) ||
|
||||
(obstacle(rel(tile, direction - 2)) && !obstacle(rel(tile, direction - 1)))){
|
||||
if(debug) Effects.effect(Fx.node4, tile.worldx(), tile.worldy());
|
||||
return tile;
|
||||
}
|
||||
}else{ //moving diagonal
|
||||
//forced neighbor in the diagonal pattern, end here
|
||||
if((obstacle(rel(tile, direction + 3)) && !obstacle(rel(tile, direction + 2)) && !obstacle(rel(tile, direction - 2))) ||
|
||||
(obstacle(rel(tile, direction - 3)) && !obstacle(rel(tile, direction - 2)) && !obstacle(rel(tile, direction + 2)))) {
|
||||
if(debug) Effects.effect(Fx.node4, tile.worldx(), tile.worldy());
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
Tile next = rel(tile, direction);
|
||||
if(obstacle(next)) break;
|
||||
tile = next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
;
|
||||
protected float estimate(Tile tile, Tile other){
|
||||
return Math.abs(tile.worldx() - other.worldx()) + Math.abs(tile.worldy() - other.worldy());
|
||||
}
|
||||
|
||||
protected int relDirection(Tile from, Tile current){
|
||||
if(from.y == current.y && from.x > current.x) return 0;
|
||||
if(from.y == current.y && from.x < current.x) return 4;
|
||||
if(from.x == current.x && from.y > current.y) return 2;
|
||||
if(from.x == current.x && from.y < current.y) return 6;
|
||||
|
||||
if(from.y > current.y && from.x > current.x) return 1;
|
||||
if(from.y < current.y && from.x < current.x) return 5;
|
||||
if(from.x > current.x && from.y < current.y) return 7;
|
||||
if(from.x < current.x && from.y > current.y) return 3;
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected void generateNodePath(Tile startNode, GraphPath<Tile> outPath) {
|
||||
|
||||
// Work back along the path, accumulating nodes
|
||||
// outPath.clear();
|
||||
while (current.from != null) {
|
||||
outPath.add(current.node);
|
||||
current = records.get(graph.getIndex(current.from));
|
||||
current = records.get(indexOf(current.from));
|
||||
}
|
||||
outPath.add(startNode);
|
||||
|
||||
@@ -201,66 +334,45 @@ public class OptimizedPathFinder<N> implements PathFinder<N> {
|
||||
outPath.reverse();
|
||||
}
|
||||
|
||||
protected void addToOpenList(NodeRecord<N> nodeRecord, float estimatedTotalCost) {
|
||||
protected void addToOpenList(NodeRecord nodeRecord, float estimatedTotalCost) {
|
||||
openList.add(nodeRecord, estimatedTotalCost);
|
||||
nodeRecord.category = OPEN;
|
||||
}
|
||||
|
||||
protected NodeRecord<N> getNodeRecord(N node) {
|
||||
if(!records.containsKey(graph.getIndex(node))){
|
||||
NodeRecord<N> record = new NodeRecord<>();
|
||||
protected NodeRecord getNodeRecord(Tile node) {
|
||||
if(!records.containsKey(indexOf(node))){
|
||||
NodeRecord record = new NodeRecord();
|
||||
record.node = node;
|
||||
record.searchId = searchId;
|
||||
records.put(graph.getIndex(node), record);
|
||||
records.put(indexOf(node), record);
|
||||
return record;
|
||||
}else{
|
||||
return records.get(graph.getIndex(node));
|
||||
NodeRecord record = records.get(indexOf(node));
|
||||
if(record.searchId != searchId){
|
||||
record.category = UNVISITED;
|
||||
record.searchId = searchId;
|
||||
}
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This nested class is used to keep track of the information we need for each node during the search.
|
||||
*
|
||||
* @param <N> Type of node
|
||||
* @author davebaol
|
||||
*/
|
||||
static class NodeRecord<N> extends BinaryHeap.Node {
|
||||
/**
|
||||
* The reference to the node.
|
||||
*/
|
||||
N node;
|
||||
N from;
|
||||
private int indexOf(Tile node){
|
||||
return node.packedPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* The incoming connection to the node
|
||||
*/
|
||||
//Connection<N> connection;
|
||||
static class NodeRecord extends BinaryHeap.Node {
|
||||
Tile node;
|
||||
Tile from;
|
||||
|
||||
/**
|
||||
* The actual cost from the start node.
|
||||
*/
|
||||
float costSoFar;
|
||||
|
||||
/**
|
||||
* The node category: {@link #UNVISITED}, {@link #OPEN} or {@link #CLOSED}.
|
||||
*/
|
||||
byte category;
|
||||
|
||||
/**
|
||||
* ID of the current search.
|
||||
*/
|
||||
int searchId;
|
||||
|
||||
/**
|
||||
* Creates a {@code NodeRecord}.
|
||||
*/
|
||||
public NodeRecord() {
|
||||
super(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the estimated total cost.
|
||||
*/
|
||||
public float getEstimatedTotalCost() {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.PathSmoother;
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import io.anuke.mindustry.entities.units.BaseUnit;
|
||||
import io.anuke.mindustry.game.SpawnPoint;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
public class Pathfind{
|
||||
/**Maximum time taken per frame on pathfinding for a single path.*/
|
||||
private static final long maxTime = 1000000 * 5;
|
||||
|
||||
/**Tile graph, for determining conenctions between two tiles*/
|
||||
TileGraph graph = new TileGraph();
|
||||
/**Smoother that removes extra nodes from a path.*/
|
||||
PathSmoother<Tile, Vector2> smoother = new PathSmoother<Tile, Vector2>(new Raycaster());
|
||||
/**temporary vector2 for calculations*/
|
||||
Vector2 vector = new Vector2();
|
||||
|
||||
Vector2 v1 = new Vector2();
|
||||
Vector2 v2 = new Vector2();
|
||||
Vector2 v3 = new Vector2();
|
||||
|
||||
/**Finds the position on the path an enemy should move to.
|
||||
* If the path is not yet calculated, this returns the enemy's position (i. e. "don't move")
|
||||
* @param enemy The enemy to find a path for
|
||||
* @return The position the enemy should move to.*/
|
||||
public Vector2 find(BaseUnit enemy){
|
||||
//TODO!
|
||||
return v1.set(enemy.x, enemy.y);
|
||||
}
|
||||
|
||||
/**Update the pathfinders and continue calculating the path if it hasn't been calculated yet.
|
||||
* This method is run each frame.*/
|
||||
public void update(){
|
||||
/*
|
||||
|
||||
//go through each spawnpoint, and if it's not found a path yet, update it
|
||||
for(int i = 0; i < world.getSpawns().size; i ++){
|
||||
SpawnPoint point = world.getSpawns().get(i);
|
||||
if(point.request == null || point.finder == null){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!point.request.pathFound){
|
||||
try{
|
||||
if(point.finder.search(point.request, maxTime)){
|
||||
smoother.smoothPath(point.path);
|
||||
point.pathTiles = point.path.nodes.toArray(Tile.class);
|
||||
point.finder = null;
|
||||
}
|
||||
}catch (ArrayIndexOutOfBoundsException e){
|
||||
//no path
|
||||
point.request.pathFound = true;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
private void resetPathFor(SpawnPoint point){
|
||||
/*
|
||||
point.finder = new OptimizedPathFinder<>(graph);
|
||||
|
||||
point.path.clear();
|
||||
|
||||
point.pathTiles = null;
|
||||
//TODO
|
||||
point.request = new PathFinderRequest<>(point.start, world.getCore(), state.difficulty.heuristic, point.path);
|
||||
point.request.statusChanged = true; //IMPORTANT!*/
|
||||
}
|
||||
|
||||
/**Finds the closest tile to a position, in an array of tiles.*/
|
||||
private int findClosest(Tile[] tiles, float x, float y){
|
||||
int cindex = -2;
|
||||
float dst = Float.MAX_VALUE;
|
||||
|
||||
for(int i = 0; i < tiles.length - 1; i ++){
|
||||
Tile tile = tiles[i];
|
||||
Tile next = tiles[i + 1];
|
||||
float d = pointLineDist(tile.worldx(), tile.worldy(), next.worldx(), next.worldy(), x, y);
|
||||
if(d < dst){
|
||||
dst = d;
|
||||
cindex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return cindex + 1;
|
||||
}
|
||||
|
||||
/**Returns whether a point is on a line.*/
|
||||
private boolean onLine(Vector2 vector, float x1, float y1, float x2, float y2){
|
||||
return MathUtils.isEqual(vector.dst(x1, y1) + vector.dst(x2, y2), Vector2.dst(x1, y1, x2, y2), 0.01f);
|
||||
}
|
||||
|
||||
/**Returns distance from a point to a line segment.*/
|
||||
private float pointLineDist(float x, float y, float x2, float y2, float px, float py){
|
||||
float l2 = Vector2.dst2(x, y, x2, y2);
|
||||
float t = Math.max(0, Math.min(1, Vector2.dot(px - x, py - y, x2 - x, y2 - y) / l2));
|
||||
Vector2 projection = v1.set(x, y).add(v2.set(x2, y2).sub(x, y).scl(t)); // Projection falls on the segment
|
||||
return projection.dst(px, py);
|
||||
}
|
||||
|
||||
//TODO documentation
|
||||
private Vector2 projectPoint(float x1, float y1, float x2, float y2, float pointx, float pointy){
|
||||
float px = x2-x1, py = y2-y1, dAB = px*px + py*py;
|
||||
float u = ((pointx - x1) * px + (pointy - y1) * py) / dAB;
|
||||
float x = x1 + u * px, y = y1 + u * py;
|
||||
return v3.set(x, y); //this is D
|
||||
}
|
||||
}
|
||||
42
core/src/io/anuke/mindustry/ai/Pathfinder.java
Normal file
42
core/src/io/anuke/mindustry/ai/Pathfinder.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package io.anuke.mindustry.ai;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.DefaultGraphPath;
|
||||
import io.anuke.mindustry.content.fx.Fx;
|
||||
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.ucore.core.Effects;
|
||||
import io.anuke.ucore.core.Events;
|
||||
import io.anuke.ucore.core.Timers;
|
||||
import io.anuke.ucore.util.Log;
|
||||
|
||||
public class Pathfinder {
|
||||
private OptimizedPathFinder finder = new OptimizedPathFinder();
|
||||
|
||||
public Pathfinder(){
|
||||
Events.on(WorldLoadEvent.class, this::clear);
|
||||
}
|
||||
|
||||
public void test(Tile start, Tile end){
|
||||
DefaultGraphPath<Tile> p = new DefaultGraphPath<>();
|
||||
/*
|
||||
OptimizedPathFinder.unop = true;
|
||||
Timers.markNs();
|
||||
finder.searchNodePath(start, end, p);
|
||||
for(Tile tile : p.nodes){
|
||||
Effects.effect(Fx.breakBlock, tile.worldx(), tile.worldy());
|
||||
}
|
||||
Log.info("Normal elapsed: {0}", Timers.elapsedNs());*/
|
||||
|
||||
OptimizedPathFinder.unop = false;
|
||||
Timers.markNs();
|
||||
finder.searchNodePath(start, end, p);
|
||||
for(Tile tile : p.nodes){
|
||||
Effects.effect(Fx.breakBlock, tile.worldx(), tile.worldy());
|
||||
}
|
||||
Log.info("JSFSAF elapsed: {0}", Timers.elapsedNs());
|
||||
}
|
||||
|
||||
private void clear(){
|
||||
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.graphics.Colors;
|
||||
import io.anuke.ucore.core.Effects.Effect;
|
||||
import io.anuke.ucore.graphics.Draw;
|
||||
import io.anuke.ucore.graphics.Fill;
|
||||
import io.anuke.ucore.graphics.Lines;
|
||||
|
||||
import static io.anuke.mindustry.Vars.respawnduration;
|
||||
import static io.anuke.mindustry.Vars.tilesize;
|
||||
|
||||
public class Fx{
|
||||
@@ -49,12 +49,26 @@ public class Fx{
|
||||
Lines.circle(e.x, e.y, 7f - e.fin() * 6f);
|
||||
Draw.reset();
|
||||
}),
|
||||
|
||||
respawn = new Effect(respawnduration, e -> {
|
||||
Draw.tcolor(Color.SCARLET);
|
||||
Draw.tscl(0.25f);
|
||||
Draw.text("Respawning in " + (int)((e.lifetime-e.time)/60), e.x, e.y);
|
||||
Draw.tscl(0.5f);
|
||||
node1 = new Effect(50, e -> {
|
||||
Lines.stroke(2f);
|
||||
Draw.color(Color.RED);
|
||||
Lines.circle(e.x, e.y, 3f);
|
||||
Draw.reset();
|
||||
}),
|
||||
node2 = new Effect(50, e -> {
|
||||
Draw.color(Color.GREEN);
|
||||
Fill.circle(e.x, e.y, 3f);
|
||||
Draw.reset();
|
||||
}),
|
||||
node3 = new Effect(50, e -> {
|
||||
Lines.stroke(1f);
|
||||
Draw.color(Color.BLUE);
|
||||
Lines.circle(e.x, e.y, 5f);
|
||||
Draw.reset();
|
||||
}),
|
||||
node4 = new Effect(50, e -> {
|
||||
Draw.color(Color.YELLOW);
|
||||
Fill.circle(e.x, e.y, 2f);
|
||||
Draw.reset();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,8 +105,6 @@ public class Control extends Module{
|
||||
|
||||
Sounds.setFalloff(9000f);
|
||||
|
||||
Musics.load("1.ogg", "2.ogg", "3.ogg", "4.ogg");
|
||||
|
||||
DefaultKeybinds.load();
|
||||
|
||||
Settings.defaultList(
|
||||
@@ -136,8 +134,6 @@ public class Control extends Module{
|
||||
});
|
||||
|
||||
Events.on(PlayEvent.class, () -> {
|
||||
renderer.clearTiles();
|
||||
|
||||
player.set(world.getSpawnX(), world.getSpawnY());
|
||||
|
||||
Core.camera.position.set(player.x, player.y, 0);
|
||||
@@ -262,8 +258,6 @@ public class Control extends Module{
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
Timers.run(1f, Musics::shuffleAll);
|
||||
|
||||
Entities.initPhysics();
|
||||
|
||||
Platform.instance.updateRPC();
|
||||
|
||||
@@ -87,9 +87,6 @@ public class Logic extends Module {
|
||||
Timers.update();
|
||||
}
|
||||
|
||||
if(!Net.client())
|
||||
world.pathfinder().update();
|
||||
|
||||
boolean gameOver = true;
|
||||
|
||||
for(TeamData data : state.teams.getTeams(true)){
|
||||
|
||||
@@ -17,6 +17,7 @@ import io.anuke.mindustry.content.fx.Fx;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.entities.Player;
|
||||
import io.anuke.mindustry.entities.SyncEntity;
|
||||
import io.anuke.mindustry.entities.effect.BelowLiquidEffect;
|
||||
import io.anuke.mindustry.entities.effect.GroundEffectEntity;
|
||||
import io.anuke.mindustry.entities.effect.GroundEffectEntity.GroundEffect;
|
||||
import io.anuke.mindustry.entities.units.BaseUnit;
|
||||
@@ -210,8 +211,9 @@ public class Renderer extends RendererModule{
|
||||
|
||||
blocks.drawFloor();
|
||||
|
||||
Entities.draw(groundEffectGroup);
|
||||
Entities.draw(groundEffectGroup, e -> e instanceof BelowLiquidEffect);
|
||||
Entities.draw(puddleGroup);
|
||||
Entities.draw(groundEffectGroup, e -> !(e instanceof BelowLiquidEffect));
|
||||
|
||||
blocks.processBlocks();
|
||||
blocks.drawBlocks(Layer.overlay);
|
||||
@@ -277,10 +279,6 @@ public class Renderer extends RendererModule{
|
||||
return minimap;
|
||||
}
|
||||
|
||||
public void clearTiles(){
|
||||
blocks.clearTiles();
|
||||
}
|
||||
|
||||
void drawPadding(){
|
||||
float vw = world.width() * tilesize;
|
||||
float cw = camera.viewportWidth * camera.zoom;
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.math.GridPoint2;
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import io.anuke.mindustry.ai.Pathfind;
|
||||
import io.anuke.mindustry.ai.Pathfinder;
|
||||
import io.anuke.mindustry.content.blocks.Blocks;
|
||||
import io.anuke.mindustry.game.EventType.TileChangeEvent;
|
||||
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
|
||||
@@ -27,7 +27,7 @@ public class World extends Module{
|
||||
|
||||
private Map currentMap;
|
||||
private Tile[][] tiles;
|
||||
private Pathfind pathfind = new Pathfind();
|
||||
private Pathfinder pathfinder = new Pathfinder();
|
||||
private Maps maps = new Maps();
|
||||
|
||||
private Array<Tile> tempTiles = new Array<>();
|
||||
@@ -46,8 +46,8 @@ public class World extends Module{
|
||||
return maps;
|
||||
}
|
||||
|
||||
public Pathfind pathfinder(){
|
||||
return pathfind;
|
||||
public Pathfinder pathfinder(){
|
||||
return pathfinder;
|
||||
}
|
||||
|
||||
//TODO proper spawnpoints!
|
||||
@@ -152,6 +152,19 @@ public class World extends Module{
|
||||
return tiles;
|
||||
}
|
||||
|
||||
/**Call to signify the beginning of map loading.
|
||||
* TileChangeEvents will not be fired until endMapLoad().*/
|
||||
public void beginMapLoad(){
|
||||
generating = true;
|
||||
}
|
||||
|
||||
/**Call to signify the end of map loading.
|
||||
* A WorldLoadEvent will be fire.*/
|
||||
public void endMapLoad(){
|
||||
generating = false;
|
||||
Events.fire(WorldLoadEvent.class);
|
||||
}
|
||||
|
||||
public void setMap(Map map){
|
||||
this.currentMap = map;
|
||||
}
|
||||
@@ -161,7 +174,7 @@ public class World extends Module{
|
||||
}
|
||||
|
||||
public void loadMap(Map map, int seed){
|
||||
generating = true;
|
||||
beginMapLoad();
|
||||
this.currentMap = map;
|
||||
this.seed = seed;
|
||||
|
||||
@@ -172,9 +185,8 @@ public class World extends Module{
|
||||
Entities.resizeTree(0, 0, width * tilesize, height * tilesize);
|
||||
|
||||
WorldGenerator.generate(tiles, MapIO.readTileData(map, true));
|
||||
generating = false;
|
||||
|
||||
Events.fire(WorldLoadEvent.class);
|
||||
endMapLoad();
|
||||
}
|
||||
|
||||
public int getSeed(){
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package io.anuke.mindustry.entities.effect;
|
||||
|
||||
public interface BelowLiquidEffect {
|
||||
}
|
||||
@@ -37,6 +37,7 @@ public class Puddle extends Entity implements SerializableEntity, Poolable{
|
||||
private static final int maxGeneration = 2;
|
||||
private static final Color tmp = new Color();
|
||||
private static final Rectangle rect = new Rectangle();
|
||||
private static int seeds;
|
||||
|
||||
private int loadedPosition = -1;
|
||||
private Tile tile;
|
||||
@@ -84,7 +85,7 @@ public class Puddle extends Entity implements SerializableEntity, Poolable{
|
||||
}else if(p.liquid == liquid){
|
||||
p.accepting = Math.max(amount, p.accepting);
|
||||
|
||||
if(generation == 0 && Timers.get(p, "ripple", 50) && p.amount >= maxLiquid/2f){
|
||||
if(generation == 0 && Timers.get(p, "ripple2", 50) && p.amount >= maxLiquid/2f){
|
||||
Effects.effect(BlockFx.ripple, p.liquid.color, (tile.worldx() + source.worldx())/2f, (tile.worldy() + source.worldy())/2f);
|
||||
}
|
||||
}else{
|
||||
@@ -172,12 +173,18 @@ public class Puddle extends Entity implements SerializableEntity, Poolable{
|
||||
|
||||
@Override
|
||||
public void draw() {
|
||||
seeds = id;
|
||||
boolean onLiquid = tile.floor().liquid;
|
||||
float f = Mathf.clamp(amount/(maxLiquid/1.5f));
|
||||
float smag = onLiquid ? 0.8f : 0f;
|
||||
float sscl = 20f;
|
||||
|
||||
Draw.color(Hue.shift(tmp.set(liquid.color), 2, -0.05f));
|
||||
Fill.circle(x, y, f * 8f);
|
||||
Fill.circle(x + Mathf.sin(Timers.time() + seeds*532, sscl, smag), y + Mathf.sin(Timers.time() + seeds*53, sscl, smag), f * 8f);
|
||||
Angles.randLenVectors(id, 3, f * 6f, (ex, ey) -> {
|
||||
Fill.circle(x + ex, y + ey, f * 5f);
|
||||
Fill.circle(x + ex + Mathf.sin(Timers.time() + seeds*532, sscl, smag),
|
||||
y + ey + Mathf.sin(Timers.time() + seeds*53, sscl, smag), f * 5f);
|
||||
seeds ++;
|
||||
});
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import io.anuke.ucore.util.Mathf;
|
||||
|
||||
import static io.anuke.mindustry.Vars.groundEffectGroup;
|
||||
|
||||
public class Rubble extends TimedEntity{
|
||||
public class Rubble extends TimedEntity implements BelowLiquidEffect{
|
||||
private static final Color color = Color.valueOf("52504e");
|
||||
private int size;
|
||||
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
package io.anuke.mindustry.game;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.Heuristic;
|
||||
import io.anuke.mindustry.ai.Heuristics.DestrutiveHeuristic;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.types.LiquidBlock;
|
||||
import io.anuke.mindustry.world.blocks.types.PowerBlock;
|
||||
import io.anuke.mindustry.world.blocks.types.defense.Turret;
|
||||
import io.anuke.mindustry.world.blocks.types.distribution.Conveyor;
|
||||
import io.anuke.mindustry.world.blocks.types.distribution.Router;
|
||||
import io.anuke.mindustry.world.blocks.types.production.Drill;
|
||||
import io.anuke.mindustry.world.blocks.types.power.PowerDistributor;
|
||||
import io.anuke.mindustry.world.blocks.types.production.Smelter;
|
||||
import io.anuke.ucore.util.Bundles;
|
||||
|
||||
public enum Difficulty {
|
||||
easy(4f, 2f, 1f, new DestrutiveHeuristic(b -> b instanceof PowerDistributor)),
|
||||
normal(2f, 1f, 1f, new DestrutiveHeuristic(b -> b instanceof Smelter || b instanceof PowerDistributor)),
|
||||
hard(1.5f, 0.5f, 0.75f, new DestrutiveHeuristic(b -> b instanceof Turret || b instanceof PowerDistributor || b instanceof Drill || b instanceof Smelter)),
|
||||
insane(0.5f, 0.25f, 0.5f, new DestrutiveHeuristic(b -> b instanceof PowerDistributor || b instanceof Drill || b instanceof Smelter || b instanceof Router)),
|
||||
purge(0.25f, 0.01f, 0.25f, new DestrutiveHeuristic(b -> b instanceof PowerDistributor || b instanceof Drill || b instanceof Router
|
||||
|| b instanceof Smelter || b instanceof Conveyor || b instanceof LiquidBlock || b instanceof PowerBlock));
|
||||
easy(4f, 2f, 1f),
|
||||
normal(2f, 1f, 1f),
|
||||
hard(1.5f, 0.5f, 0.75f),
|
||||
insane(0.5f, 0.25f, 0.5f),
|
||||
purge(0.25f, 0.01f, 0.25f);
|
||||
|
||||
/**The scaling of how many waves it takes for one more enemy of a type to appear.
|
||||
* For example: with enemeyScaling = 2 and the default scaling being 2, it would take 4 waves for
|
||||
@@ -29,13 +17,10 @@ public enum Difficulty {
|
||||
public final float timeScaling;
|
||||
/**Scaling of max time between waves. Default time is 4 minutes.*/
|
||||
public final float maxTimeScaling;
|
||||
/**Pathfdining heuristic for calculating tile costs.*/
|
||||
public final Heuristic<Tile> heuristic;
|
||||
|
||||
Difficulty(float enemyScaling, float timeScaling, float maxTimeScaling, Heuristic<Tile> heuristic){
|
||||
Difficulty(float enemyScaling, float timeScaling, float maxTimeScaling){
|
||||
this.enemyScaling = enemyScaling;
|
||||
this.timeScaling = timeScaling;
|
||||
this.heuristic = heuristic;
|
||||
this.maxTimeScaling = maxTimeScaling;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package io.anuke.mindustry.game;
|
||||
|
||||
import com.badlogic.gdx.ai.pfa.PathFinder;
|
||||
import com.badlogic.gdx.ai.pfa.PathFinderRequest;
|
||||
|
||||
import io.anuke.mindustry.ai.SmoothGraphPath;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
|
||||
public class SpawnPoint{
|
||||
public Tile start;
|
||||
public Tile[] pathTiles;
|
||||
public PathFinder<Tile> finder;
|
||||
public SmoothGraphPath path = new SmoothGraphPath();
|
||||
public PathFinderRequest<Tile> request;
|
||||
|
||||
public SpawnPoint(Tile start){
|
||||
this.start = start;
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,12 @@ import com.badlogic.gdx.utils.IntArray;
|
||||
import com.badlogic.gdx.utils.IntSet;
|
||||
import com.badlogic.gdx.utils.IntSet.IntSetIterator;
|
||||
import com.badlogic.gdx.utils.async.AsyncExecutor;
|
||||
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.types.Floor;
|
||||
import io.anuke.ucore.core.Core;
|
||||
import io.anuke.ucore.core.Events;
|
||||
import io.anuke.ucore.core.Graphics;
|
||||
import io.anuke.ucore.core.Timers;
|
||||
import io.anuke.ucore.graphics.Draw;
|
||||
@@ -32,6 +34,10 @@ public class FloorRenderer {
|
||||
private IntSet drawnLayerSet = new IntSet();
|
||||
private IntArray drawnLayers = new IntArray();
|
||||
|
||||
public FloorRenderer(){
|
||||
Events.on(WorldLoadEvent.class, this::clearTiles);
|
||||
}
|
||||
|
||||
public void drawFloor(){
|
||||
|
||||
int chunksx = world.width() / chunksize, chunksy = world.height() / chunksize;
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.anuke.ucore.core.Inputs.DeviceType;
|
||||
import io.anuke.ucore.core.KeyBinds;
|
||||
import io.anuke.ucore.core.Timers;
|
||||
import io.anuke.ucore.scene.utils.Cursors;
|
||||
import io.anuke.ucore.util.Input;
|
||||
import io.anuke.ucore.util.Mathf;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
@@ -189,6 +190,10 @@ public class DesktopInput extends InputHandler{
|
||||
shooting = true;
|
||||
}
|
||||
|
||||
if(Inputs.keyTap(Input.P)){
|
||||
world.pathfinder().test(world.tileWorld(player.x, player.y), world.tileWorld(Graphics.mouseWorld().x, Graphics.mouseWorld().y));
|
||||
}
|
||||
|
||||
if(!ui.hasMouse()) {
|
||||
if (showCursor)
|
||||
Cursors.setHand();
|
||||
|
||||
@@ -17,7 +17,7 @@ import static io.anuke.mindustry.Vars.mapExtension;
|
||||
|
||||
public class Maps implements Disposable{
|
||||
/**List of all built-in maps.*/
|
||||
private static final String[] defaultMapNames = {"test", "trinity", "routerhell", "conveyorhell"};
|
||||
private static final String[] defaultMapNames = {"test", "trinity", "routerhell", "conveyorhell", "pathfind"};
|
||||
/**Tile format version.*/
|
||||
private static final int version = 0;
|
||||
|
||||
|
||||
@@ -83,6 +83,8 @@ public class Save16 extends SaveFileVersion {
|
||||
|
||||
Entities.resizeTree(0, 0, width * tilesize, height * tilesize);
|
||||
|
||||
world.beginMapLoad();
|
||||
|
||||
Tile[][] tiles = world.createTiles(width, height);
|
||||
|
||||
for(int x = 0; x < width; x ++){
|
||||
@@ -126,7 +128,7 @@ public class Save16 extends SaveFileVersion {
|
||||
}
|
||||
}
|
||||
|
||||
if(!headless) renderer.clearTiles();
|
||||
world.endMapLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -130,6 +130,8 @@ public class NetworkIO {
|
||||
player.isAdmin = admin;
|
||||
player.add();
|
||||
|
||||
world.beginMapLoad();
|
||||
|
||||
//map
|
||||
int width = stream.readShort();
|
||||
int height = stream.readShort();
|
||||
@@ -169,6 +171,8 @@ public class NetworkIO {
|
||||
|
||||
player.set(world.getSpawnX(), world.getSpawnY());
|
||||
|
||||
world.endMapLoad();
|
||||
|
||||
}catch (IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -265,7 +265,9 @@ public class Block extends BaseBlock {
|
||||
}
|
||||
|
||||
DamageArea.dynamicExplosion(x, y, flammability, explosiveness, power, tilesize * size/2f, tempColor);
|
||||
Rubble.create(tile.drawx(), tile.drawy(), size);
|
||||
if(!tile.floor().solid && !tile.floor().liquid){
|
||||
Rubble.create(tile.drawx(), tile.drawy(), size);
|
||||
}
|
||||
}
|
||||
|
||||
/**Returns the flammability of the tile. Used for fire calculations.
|
||||
|
||||
@@ -104,7 +104,7 @@ public class Placement {
|
||||
synchronized (Entities.entityLock) {
|
||||
try {
|
||||
|
||||
rect.setSize(tilesize * 2f).setCenter(x * tilesize + type.getPlaceOffset().x, y * tilesize + type.getPlaceOffset().y);
|
||||
rect.setSize(tilesize * type.size).setCenter(x * tilesize + type.getPlaceOffset().x, y * tilesize + type.getPlaceOffset().y);
|
||||
boolean[] result = {false};
|
||||
|
||||
Units.getNearby(rect, e -> {
|
||||
|
||||
Reference in New Issue
Block a user