mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-01-28 00:19:57 +07:00
New pathfinding implementation on separate thread
This commit is contained in:
parent
bd1ea41c0d
commit
025386af53
@ -1,164 +1,249 @@
|
|||||||
package io.anuke.mindustry.ai;
|
package io.anuke.mindustry.ai;
|
||||||
|
|
||||||
import io.anuke.arc.Events;
|
import io.anuke.annotations.Annotations.*;
|
||||||
import io.anuke.arc.collection.IntArray;
|
import io.anuke.arc.*;
|
||||||
import io.anuke.arc.collection.IntQueue;
|
import io.anuke.arc.collection.*;
|
||||||
import io.anuke.arc.math.geom.Geometry;
|
import io.anuke.arc.function.*;
|
||||||
import io.anuke.arc.math.geom.Point2;
|
import io.anuke.arc.math.geom.*;
|
||||||
import io.anuke.arc.util.*;
|
import io.anuke.arc.util.*;
|
||||||
import io.anuke.mindustry.game.EventType.TileChangeEvent;
|
import io.anuke.arc.util.async.*;
|
||||||
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
|
import io.anuke.mindustry.core.GameState.*;
|
||||||
import io.anuke.mindustry.game.Team;
|
import io.anuke.mindustry.game.EventType.*;
|
||||||
import io.anuke.mindustry.game.Teams.TeamData;
|
import io.anuke.mindustry.game.*;
|
||||||
import io.anuke.mindustry.net.Net;
|
import io.anuke.mindustry.gen.*;
|
||||||
import io.anuke.mindustry.world.Pos;
|
import io.anuke.mindustry.world.*;
|
||||||
import io.anuke.mindustry.world.Tile;
|
import io.anuke.mindustry.world.meta.*;
|
||||||
import io.anuke.mindustry.world.meta.BlockFlag;
|
|
||||||
|
|
||||||
import static io.anuke.mindustry.Vars.*;
|
import static io.anuke.mindustry.Vars.*;
|
||||||
|
|
||||||
public class Pathfinder{
|
public class Pathfinder implements Runnable{
|
||||||
private static final long maxUpdate = Time.millisToNanos(4);
|
private static final long maxUpdate = Time.millisToNanos(4);
|
||||||
private PathData[] paths;
|
private static final int updateFPS = 60;
|
||||||
private IntArray blocked = new IntArray();
|
private static final int updateInterval = 1000 / updateFPS;
|
||||||
|
private static final int impassable = -1;
|
||||||
|
|
||||||
|
/** tile data, see PathTileStruct */
|
||||||
|
private int[][] tiles;
|
||||||
|
/** unordered array of path data for iteration only. DO NOT iterate ot access this in the main thread.*/
|
||||||
|
private Array<PathData> list = new Array<>();
|
||||||
|
/** Maps teams + flags to a valid path to get to that flag for that team. */
|
||||||
|
private PathData[][] pathMap = new PathData[Team.all.length][PathTarget.all.length];
|
||||||
|
/** Grid map of created path data that should not be queued again. */
|
||||||
|
private GridBits created = new GridBits(Team.all.length, PathTarget.all.length);
|
||||||
|
/** handles task scheduling on the update thread. */
|
||||||
|
private TaskQueue queue = new TaskQueue();
|
||||||
|
/** current pathfinding thread */
|
||||||
|
private @Nullable Thread thread;
|
||||||
|
|
||||||
public Pathfinder(){
|
public Pathfinder(){
|
||||||
Events.on(WorldLoadEvent.class, event -> clear());
|
Events.on(WorldLoadEvent.class, event -> {
|
||||||
Events.on(TileChangeEvent.class, event -> {
|
stop();
|
||||||
if(net.client()) return;
|
|
||||||
|
|
||||||
for(Team team : Team.all){
|
tiles = new int[world.width()][world.height()];
|
||||||
TeamData data = state.teams.get(team);
|
pathMap = new PathData[Team.all.length][PathTarget.all.length];
|
||||||
if(state.teams.isActive(team) && data.team != event.tile.getTeam()){
|
created = new GridBits(Team.all.length, PathTarget.all.length);
|
||||||
update(event.tile, data.team);
|
list = new Array<>();
|
||||||
|
|
||||||
|
for(int x = 0; x < world.width(); x++){
|
||||||
|
for(int y = 0; y < world.height(); y++){
|
||||||
|
tiles[x][y] = packTile(world.rawTile(x, y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update(event.tile, event.tile.getTeam());
|
start();
|
||||||
|
});
|
||||||
|
|
||||||
|
Events.on(TileChangeEvent.class, event -> updateTile(event.tile));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Packs a tile into its internal representation. */
|
||||||
|
private int packTile(Tile tile){
|
||||||
|
return PathTile.get(tile.cost, tile.getTeamID(), (byte)0, (!tile.solid() || tile.breakable()) && tile.floor().drownTime <= 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Starts or restarts the pathfinding thread. */
|
||||||
|
private void start(){
|
||||||
|
stop();
|
||||||
|
thread = Threads.daemon(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stops the pathfinding thread. */
|
||||||
|
private void stop(){
|
||||||
|
if(thread != null){
|
||||||
|
thread.interrupt();
|
||||||
|
thread = null;
|
||||||
|
}
|
||||||
|
queue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update a tile in the internal pathfinding grid. Causes a completely pathfinding reclaculation. */
|
||||||
|
public void updateTile(Tile tile){
|
||||||
|
if(net.client()) return;
|
||||||
|
|
||||||
|
int packed = packTile(tile);
|
||||||
|
int x = tile.x, y = tile.y;
|
||||||
|
tiles[x][y] = packed;
|
||||||
|
|
||||||
|
for(PathData[] arr : pathMap){
|
||||||
|
for(PathData path : arr){
|
||||||
|
if(path != null){
|
||||||
|
synchronized(path.targets){
|
||||||
|
path.targets.clear();
|
||||||
|
path.target.getTargets(tile.getTeam(), path.targets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.post(() -> {
|
||||||
|
for(PathData data : list){
|
||||||
|
updateTargets(data, x, y, packed);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateSolid(Tile tile){
|
/** Thread implementation. */
|
||||||
update(tile, tile.getTeam());
|
@Override
|
||||||
}
|
public void run(){
|
||||||
|
while(true){
|
||||||
|
if(net.client() || state.is(State.menu)) return;
|
||||||
|
|
||||||
public void update(){
|
queue.run();
|
||||||
if(net.client() || paths == null) return;
|
|
||||||
|
|
||||||
for(Team team : Team.all){
|
//total update time no longer than maxUpdate
|
||||||
if(state.teams.isActive(team)){
|
for(PathData data : list){
|
||||||
updateFrontier(team, maxUpdate);
|
updateFrontier(data, maxUpdate / list.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
Thread.sleep(updateInterval);
|
||||||
|
}catch(InterruptedException e){
|
||||||
|
//stop looping when interrupted externally
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tile getTargetTile(Team team, Tile tile){
|
/** Gets next tile to travel to. Main thread only. */
|
||||||
float[][] values = paths[team.ordinal()].weights;
|
public Tile getTargetTile(Tile tile, Team team, PathTarget target){
|
||||||
|
if(tile == null) return tile;
|
||||||
|
|
||||||
if(values == null || tile == null) return tile;
|
PathData data = pathMap[team.ordinal()][target.ordinal()];
|
||||||
|
|
||||||
float value = values[tile.x][tile.y];
|
if(data == null){
|
||||||
|
//if this combination is not found, create it on request
|
||||||
|
if(!created.get(team.ordinal(), target.ordinal())){
|
||||||
|
created.set(team.ordinal(), target.ordinal());
|
||||||
|
//grab targets since this is run on main thread
|
||||||
|
IntArray targets = target.getTargets(team, new IntArray());
|
||||||
|
queue.post(() -> createFor(team, target, targets));
|
||||||
|
}
|
||||||
|
return tile;
|
||||||
|
}
|
||||||
|
|
||||||
Tile target = null;
|
int[][] values = data.weights;
|
||||||
float tl = 0f;
|
int value = values[tile.x][tile.y];
|
||||||
|
|
||||||
|
Tile current = null;
|
||||||
|
int tl = 0;
|
||||||
for(Point2 point : Geometry.d8){
|
for(Point2 point : Geometry.d8){
|
||||||
int dx = tile.x + point.x, dy = tile.y + point.y;
|
int dx = tile.x + point.x, dy = tile.y + point.y;
|
||||||
|
|
||||||
Tile other = world.tile(dx, dy);
|
Tile other = world.tile(dx, dy);
|
||||||
if(other == null) continue;
|
if(other == null) continue;
|
||||||
|
|
||||||
if(values[dx][dy] < value && (target == null || values[dx][dy] < tl) &&
|
if(values[dx][dy] < value && (current == null || values[dx][dy] < tl) && !other.solid() && other.floor().drownTime <= 0 &&
|
||||||
!other.solid() && other.floor().drownTime <= 0 &&
|
|
||||||
!(point.x != 0 && point.y != 0 && (world.solid(tile.x + point.x, tile.y) || world.solid(tile.x, tile.y + point.y)))){ //diagonal corner trap
|
!(point.x != 0 && point.y != 0 && (world.solid(tile.x + point.x, tile.y) || world.solid(tile.x, tile.y + point.y)))){ //diagonal corner trap
|
||||||
target = other;
|
current = other;
|
||||||
tl = values[dx][dy];
|
tl = values[dx][dy];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target == null || tl == Float.MAX_VALUE) return tile;
|
if(current == null || tl == impassable) return tile;
|
||||||
|
|
||||||
return target;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getValueforTeam(Team team, int x, int y){
|
/** @return whether a tile can be passed through by this team. Pathfinding thread only.*/
|
||||||
return paths == null || paths[team.ordinal()].weights == null || team.ordinal() >= paths.length ? 0 : Structs.inBounds(x, y, paths[team.ordinal()].weights) ? paths[team.ordinal()].weights[x][y] : 0;
|
private boolean passable(int x, int y, Team team){
|
||||||
}
|
int tile = tiles[x][y];
|
||||||
|
return PathTile.passable(tile) || (PathTile.team(tile) != team.ordinal() && PathTile.team(tile) != Team.derelict.ordinal());
|
||||||
private boolean passable(Tile tile, Team team){
|
|
||||||
return ((!tile.solid()) || (tile.breakable() && (tile.getTeam() != team))) && tile.floor().drownTime <= 0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the frontier, increments the search and sets up all flow sources.
|
* Clears the frontier, increments the search and sets up all flow sources.
|
||||||
* This only occurs for active teams.
|
* This only occurs for active teams.
|
||||||
*/
|
*/
|
||||||
private void update(Tile tile, Team team){
|
private void updateTargets(PathData path, int x, int y, int tile){
|
||||||
//make sure team exists
|
if(!Structs.inBounds(x, y, path.weights)) return;
|
||||||
if(paths != null && paths[team.ordinal()] != null && paths[team.ordinal()].weights != null && Structs.inBounds(tile.x, tile.y, paths[team.ordinal()].weights)){
|
|
||||||
PathData path = paths[team.ordinal()];
|
|
||||||
|
|
||||||
if(path.weights[tile.x][tile.y] <= 0.1f){
|
if(path.weights[x][y] == 0){
|
||||||
//this was a previous target
|
//this was a previous target
|
||||||
path.frontier.clear();
|
|
||||||
}else if(!path.frontier.isEmpty()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//impassable tiles have a weight of float.max
|
|
||||||
if(!passable(tile, team)){
|
|
||||||
path.weights[tile.x][tile.y] = Float.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
//increment search, clear frontier
|
|
||||||
path.search++;
|
|
||||||
path.frontier.clear();
|
path.frontier.clear();
|
||||||
path.lastSearchTime = Time.millis();
|
}else if(!path.frontier.isEmpty()){
|
||||||
|
//skip if this path is processing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//add all targets to the frontier
|
//assign impassability to the tile
|
||||||
for(Tile other : indexer.getEnemy(team, BlockFlag.target)){
|
if(!passable(x, y, path.team)){
|
||||||
path.weights[other.x][other.y] = 0;
|
path.weights[x][y] = impassable;
|
||||||
path.searches[other.x][other.y] = (short)path.search;
|
}
|
||||||
path.frontier.addFirst(other.pos());
|
|
||||||
|
//increment search, clear frontier
|
||||||
|
path.search++;
|
||||||
|
path.frontier.clear();
|
||||||
|
|
||||||
|
synchronized(path.targets){
|
||||||
|
//add targets
|
||||||
|
for(int i = 0; i < path.targets.size; i++){
|
||||||
|
int pos = path.targets.get(i);
|
||||||
|
int tx = Pos.x(pos), ty = Pos.y(pos);
|
||||||
|
|
||||||
|
path.weights[tx][ty] = 0;
|
||||||
|
path.searches[tx][ty] = (short)path.search;
|
||||||
|
path.frontier.addFirst(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createFor(Team team){
|
/** Created a new flowfield that aims to get to a certain target for a certain team.
|
||||||
PathData path = new PathData();
|
* Pathfinding thread only. */
|
||||||
path.weights = new float[world.width()][world.height()];
|
private void createFor(Team team, PathTarget target, IntArray targets){
|
||||||
path.searches = new short[world.width()][world.height()];
|
PathData path = new PathData(team, target, world.width(), world.height());
|
||||||
path.search++;
|
|
||||||
path.frontier.ensureCapacity((world.width() + world.height()) * 3);
|
|
||||||
|
|
||||||
paths[team.ordinal()] = path;
|
list.add(path);
|
||||||
|
pathMap[team.ordinal()][target.ordinal()] = path;
|
||||||
|
|
||||||
|
//grab targets from passed array
|
||||||
|
synchronized(path.targets){
|
||||||
|
path.targets.clear();
|
||||||
|
path.targets.addAll(targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
//fill with impassables by default
|
||||||
for(int x = 0; x < world.width(); x++){
|
for(int x = 0; x < world.width(); x++){
|
||||||
for(int y = 0; y < world.height(); y++){
|
for(int y = 0; y < world.height(); y++){
|
||||||
Tile tile = world.tile(x, y);
|
path.weights[x][y] = impassable;
|
||||||
|
|
||||||
if(state.teams.areEnemies(tile.getTeam(), team)
|
|
||||||
&& tile.block().flags.contains(BlockFlag.target)){
|
|
||||||
path.frontier.addFirst(tile.pos());
|
|
||||||
path.weights[x][y] = 0;
|
|
||||||
path.searches[x][y] = (short)path.search;
|
|
||||||
}else{
|
|
||||||
path.weights[x][y] = Float.MAX_VALUE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFrontier(team, -1);
|
//add targets
|
||||||
|
for(int i = 0; i < path.targets.size; i++){
|
||||||
|
int pos = path.targets.get(i);
|
||||||
|
path.weights[Pos.x(pos)][Pos.y(pos)] = 0;
|
||||||
|
path.frontier.addFirst(pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateFrontier(Team team, long nsToRun){
|
/** Update the frontier for a path. Pathfinding thread only. */
|
||||||
PathData path = paths[team.ordinal()];
|
private void updateFrontier(PathData path, long nsToRun){
|
||||||
|
|
||||||
long start = Time.nanos();
|
long start = Time.nanos();
|
||||||
|
|
||||||
while(path.frontier.size > 0 && (nsToRun < 0 || Time.timeSinceNanos(start) <= nsToRun)){
|
while(path.frontier.size > 0 && (nsToRun < 0 || Time.timeSinceNanos(start) <= nsToRun)){
|
||||||
Tile tile = world.tile(path.frontier.removeLast());
|
Tile tile = world.tile(path.frontier.removeLast());
|
||||||
if(tile == null || path.weights == null) return; //something went horribly wrong, bail
|
if(tile == null || path.weights == null) return; //something went horribly wrong, bail
|
||||||
float cost = path.weights[tile.x][tile.y];
|
int cost = path.weights[tile.x][tile.y];
|
||||||
|
|
||||||
//pathfinding overflowed for some reason, time to bail. the next block update will handle this, hopefully
|
//pathfinding overflowed for some reason, time to bail. the next block update will handle this, hopefully
|
||||||
if(path.frontier.size >= world.width() * world.height()){
|
if(path.frontier.size >= world.width() * world.height()){
|
||||||
@ -166,14 +251,13 @@ public class Pathfinder{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cost < Float.MAX_VALUE){
|
if(cost != impassable){
|
||||||
for(Point2 point : Geometry.d4){
|
for(Point2 point : Geometry.d4){
|
||||||
|
|
||||||
int dx = tile.x + point.x, dy = tile.y + point.y;
|
int dx = tile.x + point.x, dy = tile.y + point.y;
|
||||||
Tile other = world.tile(dx, dy);
|
Tile other = world.tile(dx, dy);
|
||||||
|
|
||||||
if(other != null && (path.weights[dx][dy] > cost + other.cost || path.searches[dx][dy] < path.search)
|
if(other != null && (path.weights[dx][dy] > cost + other.cost || path.searches[dx][dy] < path.search) && passable(dx, dy, path.team)){
|
||||||
&& passable(other, team)){
|
|
||||||
if(other.cost < 0) throw new IllegalArgumentException("Tile cost cannot be negative! " + other);
|
if(other.cost < 0) throw new IllegalArgumentException("Tile cost cannot be negative! " + other);
|
||||||
path.frontier.addFirst(Pos.get(dx, dy));
|
path.frontier.addFirst(Pos.get(dx, dy));
|
||||||
path.weights[dx][dy] = cost + other.cost;
|
path.weights[dx][dy] = cost + other.cost;
|
||||||
@ -184,27 +268,71 @@ public class Pathfinder{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clear(){
|
/** A path target defines a set of targets for a path.*/
|
||||||
Time.mark();
|
public enum PathTarget{
|
||||||
|
enemyCores((team, out) -> {
|
||||||
paths = new PathData[Team.all.length];
|
for(Tile other : indexer.getEnemy(team, BlockFlag.core)){
|
||||||
blocked.clear();
|
out.add(other.pos());
|
||||||
|
|
||||||
for(Team team : Team.all){
|
|
||||||
PathData path = new PathData();
|
|
||||||
paths[team.ordinal()] = path;
|
|
||||||
|
|
||||||
if(state.teams.isActive(team)){
|
|
||||||
createFor(team);
|
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
rallyPoints((team, out) -> {
|
||||||
|
for(Tile other : indexer.getAllied(team, BlockFlag.rally)){
|
||||||
|
out.add(other.pos());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public static final PathTarget[] all = values();
|
||||||
|
|
||||||
|
private final BiConsumer<Team, IntArray> targeter;
|
||||||
|
|
||||||
|
PathTarget(BiConsumer<Team, IntArray> targeter){
|
||||||
|
this.targeter = targeter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get targets. This must run on the main thread.*/
|
||||||
|
public IntArray getTargets(Team team, IntArray out){
|
||||||
|
targeter.accept(team, out);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Data for a specific flow field to some set of destinations. */
|
||||||
class PathData{
|
class PathData{
|
||||||
float[][] weights;
|
/** Team this path is for. */
|
||||||
short[][] searches;
|
final Team team;
|
||||||
int search = 0;
|
/** Flag that is being targeted. */
|
||||||
long lastSearchTime;
|
final PathTarget target;
|
||||||
IntQueue frontier = new IntQueue();
|
/** costs of getting to a specific tile */
|
||||||
|
final int[][] weights;
|
||||||
|
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
|
||||||
|
final short[][] searches;
|
||||||
|
/** search frontier, these are Pos objects */
|
||||||
|
final IntQueue frontier = new IntQueue();
|
||||||
|
/** all target positions; these positions have a cost of 0, and must be synchronized on! */
|
||||||
|
final IntArray targets = new IntArray();
|
||||||
|
/** current search ID */
|
||||||
|
int search = 1;
|
||||||
|
|
||||||
|
PathData(Team team, PathTarget target, int width, int height){
|
||||||
|
this.team = team;
|
||||||
|
this.target = target;
|
||||||
|
|
||||||
|
this.weights = new int[width][height];
|
||||||
|
this.searches = new short[width][height];
|
||||||
|
this.frontier.ensureCapacity((width + height) * 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds a copy of tile data for a specific tile position. */
|
||||||
|
@Struct
|
||||||
|
class PathTileStruct{
|
||||||
|
//traversal cost
|
||||||
|
byte cost;
|
||||||
|
//team of block, if applicable (0 by default)
|
||||||
|
byte team;
|
||||||
|
//type of target; TODO remove
|
||||||
|
byte type;
|
||||||
|
//whether it's viable to pass this block
|
||||||
|
boolean passable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,8 +227,6 @@ public class Logic implements ApplicationListener{
|
|||||||
|
|
||||||
collisions.collideGroups(bulletGroup, playerGroup);
|
collisions.collideGroups(bulletGroup, playerGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
pathfinder.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!net.client() && !world.isInvalidMap() && !state.isEditor()){
|
if(!net.client() && !world.isInvalidMap() && !state.isEditor()){
|
||||||
|
@ -71,7 +71,7 @@ public abstract class FlyingUnit extends BaseUnit{
|
|||||||
public void update(){
|
public void update(){
|
||||||
if(retarget()){
|
if(retarget()){
|
||||||
targetClosest();
|
targetClosest();
|
||||||
targetClosestEnemyFlag(BlockFlag.target);
|
targetClosestEnemyFlag(BlockFlag.core);
|
||||||
|
|
||||||
if(target != null && !Units.invalidateTarget(target, team, x, y)){
|
if(target != null && !Units.invalidateTarget(target, team, x, y)){
|
||||||
setState(attack);
|
setState(attack);
|
||||||
|
@ -6,6 +6,7 @@ import io.anuke.arc.math.*;
|
|||||||
import io.anuke.arc.math.geom.*;
|
import io.anuke.arc.math.geom.*;
|
||||||
import io.anuke.arc.util.*;
|
import io.anuke.arc.util.*;
|
||||||
import io.anuke.mindustry.*;
|
import io.anuke.mindustry.*;
|
||||||
|
import io.anuke.mindustry.ai.Pathfinder.*;
|
||||||
import io.anuke.mindustry.entities.*;
|
import io.anuke.mindustry.entities.*;
|
||||||
import io.anuke.mindustry.entities.bullet.*;
|
import io.anuke.mindustry.entities.bullet.*;
|
||||||
import io.anuke.mindustry.entities.units.*;
|
import io.anuke.mindustry.entities.units.*;
|
||||||
@ -223,7 +224,7 @@ public abstract class GroundUnit extends BaseUnit{
|
|||||||
protected void moveToCore(){
|
protected void moveToCore(){
|
||||||
Tile tile = world.tileWorld(x, y);
|
Tile tile = world.tileWorld(x, y);
|
||||||
if(tile == null) return;
|
if(tile == null) return;
|
||||||
Tile targetTile = pathfinder.getTargetTile(team, tile);
|
Tile targetTile = pathfinder.getTargetTile(tile, team, PathTarget.enemyCores);
|
||||||
|
|
||||||
if(tile == targetTile) return;
|
if(tile == targetTile) return;
|
||||||
|
|
||||||
@ -246,7 +247,7 @@ public abstract class GroundUnit extends BaseUnit{
|
|||||||
|
|
||||||
Tile tile = world.tileWorld(x, y);
|
Tile tile = world.tileWorld(x, y);
|
||||||
if(tile == null) return;
|
if(tile == null) return;
|
||||||
Tile targetTile = pathfinder.getTargetTile(enemy, tile);
|
Tile targetTile = pathfinder.getTargetTile(tile, team, PathTarget.enemyCores);
|
||||||
TileEntity core = getClosestCore();
|
TileEntity core = getClosestCore();
|
||||||
|
|
||||||
if(tile == targetTile || core == null || dst(core) < 120f) return;
|
if(tile == targetTile || core == null || dst(core) < 120f) return;
|
||||||
|
@ -20,5 +20,7 @@ public enum Category{
|
|||||||
/** Things that upgrade the player such as mech pads. */
|
/** Things that upgrade the player such as mech pads. */
|
||||||
upgrade,
|
upgrade,
|
||||||
/** Things for storage or passive effects. */
|
/** Things for storage or passive effects. */
|
||||||
effect
|
effect;
|
||||||
|
|
||||||
|
public static final Category[] all = values();
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ public class PlacementFragment extends Fragment{
|
|||||||
|
|
||||||
Array<Block> returnArray = new Array<>();
|
Array<Block> returnArray = new Array<>();
|
||||||
Array<Category> returnCatArray = new Array<>();
|
Array<Category> returnCatArray = new Array<>();
|
||||||
boolean[] categoryEmpty = new boolean[Category.values().length];
|
boolean[] categoryEmpty = new boolean[Category.all.length];
|
||||||
Category currentCategory = Category.distribution;
|
Category currentCategory = Category.distribution;
|
||||||
Block hovered, lastDisplay;
|
Block hovered, lastDisplay;
|
||||||
Tile lastHover;
|
Tile lastHover;
|
||||||
@ -91,7 +91,7 @@ public class PlacementFragment extends Fragment{
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
for(KeyCode key : inputCatGrid){
|
for(KeyCode key : inputCatGrid){
|
||||||
if(Core.input.keyDown(key)){
|
if(Core.input.keyDown(key)){
|
||||||
input.block = getByCategory(Category.values()[i]).first();
|
input.block = getByCategory(Category.all[i]).first();
|
||||||
currentCategory = input.block.buildCategory;
|
currentCategory = input.block.buildCategory;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
@ -258,7 +258,7 @@ public class PlacementFragment extends Fragment{
|
|||||||
ButtonGroup<ImageButton> group = new ButtonGroup<>();
|
ButtonGroup<ImageButton> group = new ButtonGroup<>();
|
||||||
|
|
||||||
//update category empty values
|
//update category empty values
|
||||||
for(Category cat : Category.values()){
|
for(Category cat : Category.all){
|
||||||
Array<Block> blocks = getByCategory(cat);
|
Array<Block> blocks = getByCategory(cat);
|
||||||
categoryEmpty[cat.ordinal()] = blocks.isEmpty() || !unlocked(blocks.first());
|
categoryEmpty[cat.ordinal()] = blocks.isEmpty() || !unlocked(blocks.first());
|
||||||
}
|
}
|
||||||
@ -289,7 +289,7 @@ public class PlacementFragment extends Fragment{
|
|||||||
|
|
||||||
Array<Category> getCategories(){
|
Array<Category> getCategories(){
|
||||||
returnCatArray.clear();
|
returnCatArray.clear();
|
||||||
returnCatArray.addAll(Category.values());
|
returnCatArray.addAll(Category.all);
|
||||||
returnCatArray.sort((c1, c2) -> Boolean.compare(categoryEmpty[c1.ordinal()], categoryEmpty[c2.ordinal()]));
|
returnCatArray.sort((c1, c2) -> Boolean.compare(categoryEmpty[c1.ordinal()], categoryEmpty[c2.ordinal()]));
|
||||||
return returnCatArray;
|
return returnCatArray;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ public class Door extends Wall{
|
|||||||
entity.open = open;
|
entity.open = open;
|
||||||
Door door = (Door)tile.block();
|
Door door = (Door)tile.block();
|
||||||
|
|
||||||
pathfinder.updateSolid(tile);
|
pathfinder.updateTile(tile);
|
||||||
if(!entity.open){
|
if(!entity.open){
|
||||||
Effects.effect(door.openfx, tile.drawx(), tile.drawy());
|
Effects.effect(door.openfx, tile.drawx(), tile.drawy());
|
||||||
}else{
|
}else{
|
||||||
|
@ -33,7 +33,7 @@ public class CoreBlock extends StorageBlock{
|
|||||||
solid = true;
|
solid = true;
|
||||||
update = true;
|
update = true;
|
||||||
hasItems = true;
|
hasItems = true;
|
||||||
flags = EnumSet.of(BlockFlag.target, BlockFlag.producer);
|
flags = EnumSet.of(BlockFlag.core, BlockFlag.producer);
|
||||||
activeSound = Sounds.respawning;
|
activeSound = Sounds.respawning;
|
||||||
activeSoundVolume = 1f;
|
activeSoundVolume = 1f;
|
||||||
layer = Layer.overlay;
|
layer = Layer.overlay;
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package io.anuke.mindustry.world.blocks.units;
|
||||||
|
|
||||||
|
import io.anuke.arc.collection.*;
|
||||||
|
import io.anuke.mindustry.world.*;
|
||||||
|
import io.anuke.mindustry.world.meta.*;
|
||||||
|
|
||||||
|
public class RallyPoint extends Block{
|
||||||
|
|
||||||
|
public RallyPoint(String name){
|
||||||
|
super(name);
|
||||||
|
update = solid = true;
|
||||||
|
flags = EnumSet.of(BlockFlag.rally);
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,19 @@
|
|||||||
package io.anuke.mindustry.world.meta;
|
package io.anuke.mindustry.world.meta;
|
||||||
|
|
||||||
|
/** Stores special flags of blocks for easy querying. */
|
||||||
public enum BlockFlag{
|
public enum BlockFlag{
|
||||||
/** General important target for all types of units. */
|
/** Enemy core; primary target for all units. */
|
||||||
target(0),
|
core,
|
||||||
|
/** Rally point for units.*/
|
||||||
|
rally,
|
||||||
/** Producer of important goods. */
|
/** Producer of important goods. */
|
||||||
producer(Float.MAX_VALUE),
|
producer,
|
||||||
/** A turret. */
|
/** A turret. */
|
||||||
turret(Float.MAX_VALUE),
|
turret,
|
||||||
/** Only the command center block.*/
|
/** Only the command center block.*/
|
||||||
comandCenter(Float.MAX_VALUE),
|
comandCenter,
|
||||||
/** Repair point. */
|
/** Repair point. */
|
||||||
repair(Float.MAX_VALUE);
|
repair;
|
||||||
|
|
||||||
public final static BlockFlag[] all = values();
|
public final static BlockFlag[] all = values();
|
||||||
public final float cost;
|
|
||||||
|
|
||||||
BlockFlag(float cost){
|
|
||||||
if(cost < 0) throw new RuntimeException("Block flag costs cannot be < 0!");
|
|
||||||
this.cost = cost;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user