mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-02-13 12:16:53 +07:00
Dynamic pathfinding
This commit is contained in:
parent
638343d25e
commit
ed795076f0
@ -15,6 +15,7 @@ mindustry.type.Weather.WeatherComp=11
|
|||||||
mindustry.world.blocks.storage.LaunchPad.LaunchPayloadComp=12
|
mindustry.world.blocks.storage.LaunchPad.LaunchPayloadComp=12
|
||||||
oculon=13
|
oculon=13
|
||||||
phantom=14
|
phantom=14
|
||||||
|
tau=19
|
||||||
titan=15
|
titan=15
|
||||||
trident=18
|
trident=18
|
||||||
vanguard=16
|
vanguard=16
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
{fields:[{name:armor,type:float,size:4},{name:baseRotation,type:float,size:4},{name:controller,type:mindustry.entities.units.UnitController,size:-1},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:isShooting,type:boolean,size:1},{name:mineTile,type:mindustry.world.Tile,size:-1},{name:requests,type:arc.struct.Queue<mindustry.entities.units.BuildRequest>,size:-1},{name:rotation,type:float,size:4},{name:shield,type:float,size:4},{name:spawnedByCore,type:boolean,size:1},{name:stack,type:mindustry.type.ItemStack,size:-1},{name:statuses,type:arc.struct.Array<mindustry.entities.units.StatusEntry>,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
@ -34,15 +34,15 @@ public class BlockIndexer{
|
|||||||
/** Maps each team ID to a quarant. A quadrant is a grid of bits, where each bit is set if and only if there is a block of that team in that quadrant. */
|
/** Maps each team ID to a quarant. A quadrant is a grid of bits, where each bit is set if and only if there is a block of that team in that quadrant. */
|
||||||
private GridBits[] structQuadrants;
|
private GridBits[] structQuadrants;
|
||||||
/** Stores all damaged tile entities by team. */
|
/** Stores all damaged tile entities by team. */
|
||||||
private TileArray[] damagedTiles = new TileArray[Team.all().length];
|
private TileArray[] damagedTiles = new TileArray[Team.all.length];
|
||||||
/** All ores available on this map. */
|
/** All ores available on this map. */
|
||||||
private ObjectSet<Item> allOres = new ObjectSet<>();
|
private ObjectSet<Item> allOres = new ObjectSet<>();
|
||||||
/** Stores teams that are present here as tiles. */
|
/** Stores teams that are present here as tiles. */
|
||||||
private Array<Team> activeTeams = new Array<>();
|
private Array<Team> activeTeams = new Array<>();
|
||||||
/** Maps teams to a map of flagged tiles by flag. */
|
/** Maps teams to a map of flagged tiles by flag. */
|
||||||
private TileArray[][] flagMap = new TileArray[Team.all().length][BlockFlag.all.length];
|
private TileArray[][] flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
|
||||||
/** Max units by team. */
|
/** Max units by team. */
|
||||||
private int[] unitCaps = new int[Team.all().length];
|
private int[] unitCaps = new int[Team.all.length];
|
||||||
/** Maps tile positions to their last known tile index data. */
|
/** Maps tile positions to their last known tile index data. */
|
||||||
private IntMap<TileIndex> typeMap = new IntMap<>();
|
private IntMap<TileIndex> typeMap = new IntMap<>();
|
||||||
/** Empty set used for returning. */
|
/** Empty set used for returning. */
|
||||||
@ -69,9 +69,9 @@ public class BlockIndexer{
|
|||||||
Events.on(WorldLoadEvent.class, event -> {
|
Events.on(WorldLoadEvent.class, event -> {
|
||||||
scanOres.clear();
|
scanOres.clear();
|
||||||
scanOres.addAll(Item.getAllOres());
|
scanOres.addAll(Item.getAllOres());
|
||||||
damagedTiles = new TileArray[Team.all().length];
|
damagedTiles = new TileArray[Team.all.length];
|
||||||
flagMap = new TileArray[Team.all().length][BlockFlag.all.length];
|
flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
|
||||||
unitCaps = new int[Team.all().length];
|
unitCaps = new int[Team.all.length];
|
||||||
|
|
||||||
for(int i = 0; i < flagMap.length; i++){
|
for(int i = 0; i < flagMap.length; i++){
|
||||||
for(int j = 0; j < BlockFlag.all.length; j++){
|
for(int j = 0; j < BlockFlag.all.length; j++){
|
||||||
@ -84,7 +84,7 @@ public class BlockIndexer{
|
|||||||
ores = null;
|
ores = null;
|
||||||
|
|
||||||
//create bitset for each team type that contains each quadrant
|
//create bitset for each team type that contains each quadrant
|
||||||
structQuadrants = new GridBits[Team.all().length];
|
structQuadrants = new GridBits[Team.all.length];
|
||||||
|
|
||||||
for(Tile tile : world.tiles){
|
for(Tile tile : world.tiles){
|
||||||
process(tile);
|
process(tile);
|
||||||
|
@ -21,18 +21,21 @@ public class Pathfinder implements Runnable{
|
|||||||
private static final int updateFPS = 60;
|
private static final int updateFPS = 60;
|
||||||
private static final int updateInterval = 1000 / updateFPS;
|
private static final int updateInterval = 1000 / updateFPS;
|
||||||
private static final int impassable = -1;
|
private static final int impassable = -1;
|
||||||
|
private static final int fieldTimeout = 1000 * 60 * 2;
|
||||||
|
|
||||||
/** tile data, see PathTileStruct */
|
/** tile data, see PathTileStruct */
|
||||||
private int[][] tiles;
|
private int[][] tiles;
|
||||||
/** unordered array of path data for iteration only. DO NOT iterate ot access this in the main thread. */
|
/** unordered array of path data for iteration only. DO NOT iterate or access this in the main thread. */
|
||||||
private Array<PathData> list = new Array<>();
|
private Array<Flowfield> threadList = new Array<>(), mainList = new Array<>();
|
||||||
/** Maps teams + flags to a valid path to get to that flag for that team. */
|
/** Maps team ID and target to to a flowfield.*/
|
||||||
private PathData[][] pathMap = new PathData[Team.all().length][PathTarget.all.length];
|
private ObjectMap<PathTarget, Flowfield>[] fieldMap = new ObjectMap[Team.all.length];
|
||||||
/** Grid map of created path data that should not be queued again. */
|
/** Used field maps. */
|
||||||
private GridBits created = new GridBits(Team.all().length, PathTarget.all.length);
|
private ObjectSet<PathTarget>[] fieldMapUsed = new ObjectSet[Team.all.length];
|
||||||
/** handles task scheduling on the update thread. */
|
/** handles task scheduling on the update thread. */
|
||||||
private TaskQueue queue = new TaskQueue();
|
private TaskQueue queue = new TaskQueue();
|
||||||
/** current pathfinding thread */
|
/** Stores path target for a position. Main thread only.*/
|
||||||
|
private ObjectMap<Position, PathTarget> targetCache = new ObjectMap<>();
|
||||||
|
/** Current pathfinding thread */
|
||||||
private @Nullable Thread thread;
|
private @Nullable Thread thread;
|
||||||
|
|
||||||
public Pathfinder(){
|
public Pathfinder(){
|
||||||
@ -41,16 +44,18 @@ public class Pathfinder implements Runnable{
|
|||||||
|
|
||||||
//reset and update internal tile array
|
//reset and update internal tile array
|
||||||
tiles = new int[world.width()][world.height()];
|
tiles = new int[world.width()][world.height()];
|
||||||
pathMap = new PathData[Team.all().length][PathTarget.all.length];
|
fieldMap = new ObjectMap[Team.all.length];
|
||||||
created = new GridBits(Team.all().length, PathTarget.all.length);
|
fieldMapUsed = new ObjectSet[Team.all.length];
|
||||||
list = new Array<>();
|
targetCache = new ObjectMap<>();
|
||||||
|
threadList = new Array<>();
|
||||||
|
mainList = new Array<>();
|
||||||
|
|
||||||
for(Tile tile : world.tiles){
|
for(Tile tile : world.tiles){
|
||||||
tiles[tile.x][tile.y] = packTile(tile);
|
tiles[tile.x][tile.y] = packTile(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
//special preset which may help speed things up; this is optional
|
//special preset which may help speed things up; this is optional
|
||||||
preloadPath(state.rules.waveTeam, PathTarget.enemyCores);
|
preloadPath(state.rules.waveTeam, FlagTarget.enemyCores);
|
||||||
|
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
@ -62,7 +67,7 @@ public class Pathfinder implements Runnable{
|
|||||||
|
|
||||||
/** Packs a tile into its internal representation. */
|
/** Packs a tile into its internal representation. */
|
||||||
private int packTile(Tile tile){
|
private int packTile(Tile tile){
|
||||||
return PathTile.get(tile.cost, tile.getTeamID(), !tile.solid() && tile.floor().drownTime <= 0f);
|
return PathTile.get(tile.cost, tile.getTeamID(), !tile.solid() && tile.floor().drownTime <= 0f, !tile.solid() && tile.floor().isLiquid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Starts or restarts the pathfinding thread. */
|
/** Starts or restarts the pathfinding thread. */
|
||||||
@ -80,12 +85,13 @@ public class Pathfinder implements Runnable{
|
|||||||
queue.clear();
|
queue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int debugValue(Team team, int x, int y){
|
//public int debugValue(Team team, int x, int y){
|
||||||
if(pathMap[team.id][PathTarget.enemyCores.ordinal()] == null) return 0;
|
// if(pathMap[team.id][FlagTarget.enemyCores.ordinal()] == null) return 0;
|
||||||
return pathMap[team.id][PathTarget.enemyCores.ordinal()].weights[x][y];
|
// return pathMap[team.id][FlagTarget.enemyCores.ordinal()].weights[x][y];
|
||||||
}
|
//}
|
||||||
|
|
||||||
/** Update a tile in the internal pathfinding grid. Causes a complete pathfinding reclaculation. */
|
/** Update a tile in the internal pathfinding grid.
|
||||||
|
* Causes a complete pathfinding reclaculation. Main thread only. */
|
||||||
public void updateTile(Tile tile){
|
public void updateTile(Tile tile){
|
||||||
if(net.client()) return;
|
if(net.client()) return;
|
||||||
|
|
||||||
@ -98,19 +104,17 @@ public class Pathfinder implements Runnable{
|
|||||||
});
|
});
|
||||||
|
|
||||||
//can't iterate through array so use the map, which should not lead to problems
|
//can't iterate through array so use the map, which should not lead to problems
|
||||||
for(PathData[] arr : pathMap){
|
for(Flowfield path : mainList){
|
||||||
for(PathData path : arr){
|
if(path != null){
|
||||||
if(path != null){
|
synchronized(path.targets){
|
||||||
synchronized(path.targets){
|
path.targets.clear();
|
||||||
path.targets.clear();
|
path.target.getPositions(path.team, path.targets);
|
||||||
path.target.getTargets(path.team, path.targets);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.post(() -> {
|
queue.post(() -> {
|
||||||
for(PathData data : list){
|
for(Flowfield data : threadList){
|
||||||
updateTargets(data, x, y);
|
updateTargets(data, x, y);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -126,8 +130,31 @@ public class Pathfinder implements Runnable{
|
|||||||
queue.run();
|
queue.run();
|
||||||
|
|
||||||
//total update time no longer than maxUpdate
|
//total update time no longer than maxUpdate
|
||||||
for(PathData data : list){
|
for(Flowfield data : threadList){
|
||||||
updateFrontier(data, maxUpdate / list.size);
|
updateFrontier(data, maxUpdate / threadList.size);
|
||||||
|
|
||||||
|
//remove flowfields that have 'timed out' so they can be garbage collected and no longer waste space
|
||||||
|
if(data.target.refreshRate() > 0 && Time.timeSinceMillis(data.lastUpdateTime) > fieldTimeout){
|
||||||
|
//make sure it doesn't get removed twice
|
||||||
|
data.lastUpdateTime = Time.millis();
|
||||||
|
|
||||||
|
Team team = data.team;
|
||||||
|
|
||||||
|
Core.app.post(() -> {
|
||||||
|
//remove its used state
|
||||||
|
if(fieldMap[team.uid] != null){
|
||||||
|
fieldMap[team.uid].remove(data.target);
|
||||||
|
fieldMapUsed[team.uid].remove(data.target);
|
||||||
|
}
|
||||||
|
//remove from main thread list
|
||||||
|
mainList.remove(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
queue.post(() -> {
|
||||||
|
//remove from this thread list with a delay
|
||||||
|
threadList.remove(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
@ -142,23 +169,44 @@ public class Pathfinder implements Runnable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Tile getTargetTile(Tile tile, Team team, Position target){
|
||||||
|
return getTargetTile(tile, team, getTarget(target));
|
||||||
|
}
|
||||||
|
|
||||||
/** Gets next tile to travel to. Main thread only. */
|
/** Gets next tile to travel to. Main thread only. */
|
||||||
public Tile getTargetTile(Tile tile, Team team, PathTarget target){
|
public Tile getTargetTile(Tile tile, Team team, PathTarget target){
|
||||||
if(tile == null) return null;
|
if(tile == null) return null;
|
||||||
|
|
||||||
PathData data = pathMap[team.id][target.ordinal()];
|
if(fieldMap[team.uid] == null){
|
||||||
|
fieldMap[team.uid] = new ObjectMap<>();
|
||||||
|
fieldMapUsed[team.uid] = new ObjectSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Flowfield data = fieldMap[team.uid].get(target);
|
||||||
|
|
||||||
if(data == null){
|
if(data == null){
|
||||||
//if this combination is not found, create it on request
|
//if this combination is not found, create it on request
|
||||||
if(!created.get(team.id, target.ordinal())){
|
if(fieldMapUsed[team.uid].add(target)){
|
||||||
created.set(team.id, target.ordinal());
|
|
||||||
//grab targets since this is run on main thread
|
//grab targets since this is run on main thread
|
||||||
IntArray targets = target.getTargets(team, new IntArray());
|
IntArray targets = target.getPositions(team, new IntArray());
|
||||||
queue.post(() -> createPath(team, target, targets));
|
queue.post(() -> createPath(team, target, targets));
|
||||||
}
|
}
|
||||||
return tile;
|
return tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if refresh rate is positive, queue a refresh
|
||||||
|
if(target.refreshRate() > 0 && Time.timeSinceMillis(data.lastUpdateTime) > target.refreshRate()){
|
||||||
|
data.lastUpdateTime = Time.millis();
|
||||||
|
|
||||||
|
synchronized(data.targets){
|
||||||
|
data.targets.clear();
|
||||||
|
data.target.getPositions(data.team, data.targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
//queue an update
|
||||||
|
queue.post(() -> updateTargets(data));
|
||||||
|
}
|
||||||
|
|
||||||
int[][] values = data.weights;
|
int[][] values = data.weights;
|
||||||
int value = values[tile.x][tile.y];
|
int value = values[tile.x][tile.y];
|
||||||
|
|
||||||
@ -182,6 +230,10 @@ public class Pathfinder implements Runnable{
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PathTarget getTarget(Position position){
|
||||||
|
return targetCache.getOr(position, () -> new PositionTarget(position));
|
||||||
|
}
|
||||||
|
|
||||||
/** @return whether a tile can be passed through by this team. Pathfinding thread only. */
|
/** @return whether a tile can be passed through by this team. Pathfinding thread only. */
|
||||||
private boolean passable(int x, int y, Team team){
|
private boolean passable(int x, int y, Team team){
|
||||||
int tile = tiles[x][y];
|
int tile = tiles[x][y];
|
||||||
@ -192,7 +244,7 @@ public class Pathfinder implements Runnable{
|
|||||||
* 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 updateTargets(PathData path, int x, int y){
|
private void updateTargets(Flowfield path, int x, int y){
|
||||||
if(!Structs.inBounds(x, y, path.weights)) return;
|
if(!Structs.inBounds(x, y, path.weights)) return;
|
||||||
|
|
||||||
if(path.weights[x][y] == 0){
|
if(path.weights[x][y] == 0){
|
||||||
@ -208,10 +260,18 @@ public class Pathfinder implements Runnable{
|
|||||||
path.weights[x][y] = impassable;
|
path.weights[x][y] = impassable;
|
||||||
}
|
}
|
||||||
|
|
||||||
//increment search, clear frontier
|
//clear frontier to prevent contamination
|
||||||
path.search++;
|
|
||||||
path.frontier.clear();
|
path.frontier.clear();
|
||||||
|
|
||||||
|
updateTargets(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Increments the search and sets up flow sources. Does not change the frontier. */
|
||||||
|
private void updateTargets(Flowfield path){
|
||||||
|
|
||||||
|
//increment search, but do not clear the frontier
|
||||||
|
path.search++;
|
||||||
|
|
||||||
synchronized(path.targets){
|
synchronized(path.targets){
|
||||||
//add targets
|
//add targets
|
||||||
for(int i = 0; i < path.targets.size; i++){
|
for(int i = 0; i < path.targets.size; i++){
|
||||||
@ -226,18 +286,26 @@ public class Pathfinder implements Runnable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void preloadPath(Team team, PathTarget target){
|
private void preloadPath(Team team, PathTarget target){
|
||||||
updateFrontier(createPath(team, target, target.getTargets(team, new IntArray())), -1);
|
updateFrontier(createPath(team, target, target.getPositions(team, new IntArray())), -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created a new flowfield that aims to get to a certain target for a certain team.
|
* Created a new flowfield that aims to get to a certain target for a certain team.
|
||||||
* Pathfinding thread only.
|
* Pathfinding thread only.
|
||||||
*/
|
*/
|
||||||
private PathData createPath(Team team, PathTarget target, IntArray targets){
|
private Flowfield createPath(Team team, PathTarget target, IntArray targets){
|
||||||
PathData path = new PathData(team, target, world.width(), world.height());
|
Flowfield path = new Flowfield(team, target, world.width(), world.height());
|
||||||
|
path.lastUpdateTime = Time.millis();
|
||||||
|
|
||||||
list.add(path);
|
threadList.add(path);
|
||||||
pathMap[team.id][target.ordinal()] = path;
|
|
||||||
|
//add to main thread's list of paths
|
||||||
|
Core.app.post(() -> {
|
||||||
|
mainList.add(path);
|
||||||
|
if(fieldMap[team.uid] != null){
|
||||||
|
fieldMap[team.uid].put(target, path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//grab targets from passed array
|
//grab targets from passed array
|
||||||
synchronized(path.targets){
|
synchronized(path.targets){
|
||||||
@ -263,7 +331,7 @@ public class Pathfinder implements Runnable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Update the frontier for a path. Pathfinding thread only. */
|
/** Update the frontier for a path. Pathfinding thread only. */
|
||||||
private void updateFrontier(PathData path, long nsToRun){
|
private void updateFrontier(Flowfield 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)){
|
||||||
@ -295,7 +363,7 @@ public class Pathfinder implements Runnable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** A path target defines a set of targets for a path. */
|
/** A path target defines a set of targets for a path. */
|
||||||
public enum PathTarget{
|
public enum FlagTarget implements PathTarget{
|
||||||
enemyCores((team, out) -> {
|
enemyCores((team, out) -> {
|
||||||
for(Tile other : indexer.getEnemy(team, BlockFlag.core)){
|
for(Tile other : indexer.getEnemy(team, BlockFlag.core)){
|
||||||
out.add(other.pos());
|
out.add(other.pos());
|
||||||
@ -314,23 +382,54 @@ public class Pathfinder implements Runnable{
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
public static final PathTarget[] all = values();
|
public static final FlagTarget[] all = values();
|
||||||
|
|
||||||
private final Cons2<Team, IntArray> targeter;
|
private final Cons2<Team, IntArray> targeter;
|
||||||
|
|
||||||
PathTarget(Cons2<Team, IntArray> targeter){
|
FlagTarget(Cons2<Team, IntArray> targeter){
|
||||||
this.targeter = targeter;
|
this.targeter = targeter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get targets. This must run on the main thread. */
|
@Override
|
||||||
public IntArray getTargets(Team team, IntArray out){
|
public IntArray getPositions(Team team, IntArray out){
|
||||||
targeter.get(team, out);
|
targeter.get(team, out);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int refreshRate(){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PositionTarget implements PathTarget{
|
||||||
|
public final Position position;
|
||||||
|
|
||||||
|
public PositionTarget(Position position){
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntArray getPositions(Team team, IntArray out){
|
||||||
|
out.add(Point2.pack(world.toTile(position.getX()), world.toTile(position.getY())));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int refreshRate(){
|
||||||
|
return 1000 * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface PathTarget{
|
||||||
|
/** Gets targets to pathfind towards. This must run on the main thread. */
|
||||||
|
IntArray getPositions(Team team, IntArray out);
|
||||||
|
/** Refresh rate in milliseconds. Return any number <= 0 to disable. */
|
||||||
|
int refreshRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Data for a specific flow field to some set of destinations. */
|
/** Data for a specific flow field to some set of destinations. */
|
||||||
static class PathData{
|
static class Flowfield{
|
||||||
/** Team this path is for. */
|
/** Team this path is for. */
|
||||||
final Team team;
|
final Team team;
|
||||||
/** Flag that is being targeted. */
|
/** Flag that is being targeted. */
|
||||||
@ -345,8 +444,10 @@ public class Pathfinder implements Runnable{
|
|||||||
final IntArray targets = new IntArray();
|
final IntArray targets = new IntArray();
|
||||||
/** current search ID */
|
/** current search ID */
|
||||||
int search = 1;
|
int search = 1;
|
||||||
|
/** last updated time */
|
||||||
|
long lastUpdateTime;
|
||||||
|
|
||||||
PathData(Team team, PathTarget target, int width, int height){
|
Flowfield(Team team, PathTarget target, int width, int height){
|
||||||
this.team = team;
|
this.team = team;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
|
|
||||||
@ -363,9 +464,9 @@ public class Pathfinder implements Runnable{
|
|||||||
short cost;
|
short cost;
|
||||||
//team of block, if applicable (0 by default)
|
//team of block, if applicable (0 by default)
|
||||||
byte team;
|
byte team;
|
||||||
//type of target; TODO remove
|
|
||||||
//byte type;
|
|
||||||
//whether it's viable to pass this block
|
//whether it's viable to pass this block
|
||||||
boolean passable;
|
boolean passable;
|
||||||
|
//whether it's viable to pass this block through water
|
||||||
|
boolean passableWater;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package mindustry.ai.types;
|
package mindustry.ai.types;
|
||||||
|
|
||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
|
import mindustry.*;
|
||||||
import mindustry.ai.formations.*;
|
import mindustry.ai.formations.*;
|
||||||
import mindustry.entities.units.*;
|
import mindustry.entities.units.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
@ -28,14 +29,20 @@ public class FormationAI extends AIController implements FormationMember{
|
|||||||
if(leader.isShooting()){
|
if(leader.isShooting()){
|
||||||
unit.aimLook(leader.aimX(), leader.aimY());
|
unit.aimLook(leader.aimX(), leader.aimY());
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
unit.lookAt(leader.rotation());
|
|
||||||
if(!unit.vel().isZero(0.001f)){
|
if(!unit.vel().isZero(0.001f)){
|
||||||
// unit.lookAt(unit.vel().angle());
|
unit.lookAt(unit.vel().angle());
|
||||||
|
}else{
|
||||||
|
unit.lookAt(leader.rotation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
|
Vec2 realtarget = vec.set(target);
|
||||||
|
|
||||||
|
if(unit.isGrounded() && Vars.world.raycast(unit.tileX(), unit.tileY(), leader.tileX(), leader.tileY(), Vars.world::solid)){
|
||||||
|
realtarget.set(Vars.pathfinder.getTargetTile(unit.tileOn(), unit.team(), leader));
|
||||||
|
}
|
||||||
|
|
||||||
|
unit.moveAt(realtarget.sub(unit).limit(unit.type().speed));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -38,7 +38,7 @@ public class GroundAI extends AIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(dst > unit.range() * 0.5f){
|
if(dst > unit.range() * 0.5f){
|
||||||
moveToCore(PathTarget.enemyCores);
|
moveToCore(FlagTarget.enemyCores);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ public class GroundAI extends AIController{
|
|||||||
unit.controlWeapons(rotate, shoot);
|
unit.controlWeapons(rotate, shoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void moveToCore(PathTarget path){
|
protected void moveToCore(FlagTarget path){
|
||||||
Tile tile = unit.tileOn();
|
Tile tile = unit.tileOn();
|
||||||
if(tile == null) return;
|
if(tile == null) return;
|
||||||
Tile targetTile = pathfinder.getTargetTile(tile, unit.team(), path);
|
Tile targetTile = pathfinder.getTargetTile(tile, unit.team(), path);
|
||||||
@ -86,7 +86,7 @@ public class GroundAI extends AIController{
|
|||||||
|
|
||||||
Tile tile = unit.tileOn();
|
Tile tile = unit.tileOn();
|
||||||
if(tile == null) return;
|
if(tile == null) return;
|
||||||
Tile targetTile = pathfinder.getTargetTile(tile, enemy, PathTarget.enemyCores);
|
Tile targetTile = pathfinder.getTargetTile(tile, enemy, FlagTarget.enemyCores);
|
||||||
Tilec core = unit.closestCore();
|
Tilec core = unit.closestCore();
|
||||||
|
|
||||||
if(tile == targetTile || core == null || unit.within(core, 120f)) return;
|
if(tile == targetTile || core == null || unit.within(core, 120f)) return;
|
||||||
|
@ -9,8 +9,8 @@ import java.util.*;
|
|||||||
|
|
||||||
/** Creates quadtrees per unit team. */
|
/** Creates quadtrees per unit team. */
|
||||||
public class TeamIndexProcess implements AsyncProcess{
|
public class TeamIndexProcess implements AsyncProcess{
|
||||||
private QuadTree<Unitc>[] trees = new QuadTree[Team.all().length];
|
private QuadTree<Unitc>[] trees = new QuadTree[Team.all.length];
|
||||||
private int[] counts = new int[Team.all().length];
|
private int[] counts = new int[Team.all.length];
|
||||||
|
|
||||||
public QuadTree<Unitc> tree(Team team){
|
public QuadTree<Unitc> tree(Team team){
|
||||||
if(trees[team.uid] == null) trees[team.uid] = new QuadTree<>(Vars.world.getQuadBounds(new Rect()));
|
if(trees[team.uid] == null) trees[team.uid] = new QuadTree<>(Vars.world.getQuadBounds(new Rect()));
|
||||||
@ -28,14 +28,14 @@ public class TeamIndexProcess implements AsyncProcess{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset(){
|
public void reset(){
|
||||||
counts = new int[Team.all().length];
|
counts = new int[Team.all.length];
|
||||||
trees = new QuadTree[Team.all().length];
|
trees = new QuadTree[Team.all.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void begin(){
|
public void begin(){
|
||||||
|
|
||||||
for(Team team : Team.all()){
|
for(Team team : Team.all){
|
||||||
if(trees[team.uid] != null){
|
if(trees[team.uid] != null){
|
||||||
trees[team.uid].clear();
|
trees[team.uid].clear();
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,10 @@ public class UnitTypes implements ContentList{
|
|||||||
public static @EntityDef({Unitc.class, Mechc.class}) UnitType titan, dagger, crawler, fortress, eruptor, chaosArray, eradicator;
|
public static @EntityDef({Unitc.class, Mechc.class}) UnitType titan, dagger, crawler, fortress, eruptor, chaosArray, eradicator;
|
||||||
|
|
||||||
//ground + builder
|
//ground + builder
|
||||||
public static @EntityDef({Unitc.class, Mechc.class, Builderc.class}) UnitType oculon, tau;
|
public static @EntityDef({Unitc.class, Mechc.class, Builderc.class}) UnitType tau;
|
||||||
|
|
||||||
|
//ground + builder + miner + commander
|
||||||
|
public static @EntityDef({Unitc.class, Mechc.class, Builderc.class, Minerc.class, Commanderc.class}) UnitType oculon;
|
||||||
|
|
||||||
//legs
|
//legs
|
||||||
public static @EntityDef({Unitc.class, Legsc.class}) UnitType cix;
|
public static @EntityDef({Unitc.class, Legsc.class}) UnitType cix;
|
||||||
|
@ -349,6 +349,35 @@ public class World{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean raycast(int x0f, int y0f, int x1, int y1, Raycaster cons){
|
||||||
|
int x0 = x0f;
|
||||||
|
int y0 = y0f;
|
||||||
|
int dx = Math.abs(x1 - x0);
|
||||||
|
int dy = Math.abs(y1 - y0);
|
||||||
|
|
||||||
|
int sx = x0 < x1 ? 1 : -1;
|
||||||
|
int sy = y0 < y1 ? 1 : -1;
|
||||||
|
|
||||||
|
int err = dx - dy;
|
||||||
|
int e2;
|
||||||
|
while(true){
|
||||||
|
|
||||||
|
if(cons.accept(x0, y0)) return true;
|
||||||
|
if(x0 == x1 && y0 == y1) return false;
|
||||||
|
|
||||||
|
e2 = 2 * err;
|
||||||
|
if(e2 > -dy){
|
||||||
|
err = err - dy;
|
||||||
|
x0 = x0 + sx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e2 < dx){
|
||||||
|
err = err + dx;
|
||||||
|
y0 = y0 + sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addDarkness(Tiles tiles){
|
public void addDarkness(Tiles tiles){
|
||||||
byte[] dark = new byte[tiles.width * tiles.height];
|
byte[] dark = new byte[tiles.width * tiles.height];
|
||||||
byte[] writeBuffer = new byte[tiles.width * tiles.height];
|
byte[] writeBuffer = new byte[tiles.width * tiles.height];
|
||||||
|
@ -529,7 +529,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
|||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
for(Team team : Team.base()){
|
for(Team team : Team.baseTeams){
|
||||||
ImageButton button = new ImageButton(Tex.whiteui, Styles.clearTogglePartiali);
|
ImageButton button = new ImageButton(Tex.whiteui, Styles.clearTogglePartiali);
|
||||||
button.margin(4f);
|
button.margin(4f);
|
||||||
button.getImageCell().grow();
|
button.getImageCell().grow();
|
||||||
|
@ -19,9 +19,9 @@ public class Team implements Comparable<Team>{
|
|||||||
public String name;
|
public String name;
|
||||||
|
|
||||||
/** All 256 registered teams. */
|
/** All 256 registered teams. */
|
||||||
private static final Team[] all = new Team[256];
|
public static final Team[] all = new Team[256];
|
||||||
/** The 6 base teams used in the editor. */
|
/** The 6 base teams used in the editor. */
|
||||||
private static final Team[] baseTeams = new Team[6];
|
public static final Team[] baseTeams = new Team[6];
|
||||||
|
|
||||||
public final static Team
|
public final static Team
|
||||||
derelict = new Team(0, "derelict", Color.valueOf("4d4e58")),
|
derelict = new Team(0, "derelict", Color.valueOf("4d4e58")),
|
||||||
@ -44,16 +44,6 @@ public class Team implements Comparable<Team>{
|
|||||||
return all[((byte)id) & 0xff];
|
return all[((byte)id) & 0xff];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return the 6 base team colors. */
|
|
||||||
public static Team[] base(){
|
|
||||||
return baseTeams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return all the teams - do not use this for lookup! */
|
|
||||||
public static Team[] all(){
|
|
||||||
return all;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Team(int id, String name, Color color){
|
protected Team(int id, String name, Color color){
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
|
@ -183,7 +183,7 @@ public class HudFragment extends Fragment{
|
|||||||
t.table(teams -> {
|
t.table(teams -> {
|
||||||
teams.left();
|
teams.left();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(Team team : Team.base()){
|
for(Team team : Team.baseTeams){
|
||||||
ImageButton button = teams.button(Tex.whiteui, Styles.clearTogglePartiali, 40f, () -> Call.setPlayerTeamEditor(player, team))
|
ImageButton button = teams.button(Tex.whiteui, Styles.clearTogglePartiali, 40f, () -> Call.setPlayerTeamEditor(player, team))
|
||||||
.size(50f).margin(6f).get();
|
.size(50f).margin(6f).get();
|
||||||
button.getImageCell().grow();
|
button.getImageCell().grow();
|
||||||
|
@ -405,7 +405,7 @@ public class ServerControl implements ApplicationListener{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Team team = arg.length == 0 ? Team.sharded : Structs.find(Team.all(), t -> t.name.equals(arg[0]));
|
Team team = arg.length == 0 ? Team.sharded : Structs.find(Team.all, t -> t.name.equals(arg[0]));
|
||||||
|
|
||||||
if(team == null){
|
if(team == null){
|
||||||
err("No team with that name found.");
|
err("No team with that name found.");
|
||||||
|
Loading…
Reference in New Issue
Block a user