From ed795076f0fe9f219cb81c40be9ceea72d38a682 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 28 May 2020 11:27:42 -0400 Subject: [PATCH] Dynamic pathfinding --- .../src/main/resources/classids.properties | 1 + .../0.json | 1 + core/src/mindustry/ai/BlockIndexer.java | 14 +- core/src/mindustry/ai/Pathfinder.java | 199 +++++++++++++----- core/src/mindustry/ai/types/FormationAI.java | 15 +- core/src/mindustry/ai/types/GroundAI.java | 6 +- .../src/mindustry/async/TeamIndexProcess.java | 10 +- core/src/mindustry/content/UnitTypes.java | 5 +- core/src/mindustry/core/World.java | 29 +++ .../src/mindustry/editor/MapEditorDialog.java | 2 +- core/src/mindustry/game/Team.java | 14 +- .../mindustry/ui/fragments/HudFragment.java | 2 +- .../src/mindustry/server/ServerControl.java | 2 +- 13 files changed, 216 insertions(+), 84 deletions(-) create mode 100644 annotations/src/main/resources/revisions/BuilderCommanderMechMinerUnitEntity/0.json diff --git a/annotations/src/main/resources/classids.properties b/annotations/src/main/resources/classids.properties index 5f5e95830e..92e667b660 100644 --- a/annotations/src/main/resources/classids.properties +++ b/annotations/src/main/resources/classids.properties @@ -15,6 +15,7 @@ mindustry.type.Weather.WeatherComp=11 mindustry.world.blocks.storage.LaunchPad.LaunchPayloadComp=12 oculon=13 phantom=14 +tau=19 titan=15 trident=18 vanguard=16 diff --git a/annotations/src/main/resources/revisions/BuilderCommanderMechMinerUnitEntity/0.json b/annotations/src/main/resources/revisions/BuilderCommanderMechMinerUnitEntity/0.json new file mode 100644 index 0000000000..dcf10887d1 --- /dev/null +++ b/annotations/src/main/resources/revisions/BuilderCommanderMechMinerUnitEntity/0.json @@ -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,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,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}]} \ No newline at end of file diff --git a/core/src/mindustry/ai/BlockIndexer.java b/core/src/mindustry/ai/BlockIndexer.java index 0ba659e460..798312578b 100644 --- a/core/src/mindustry/ai/BlockIndexer.java +++ b/core/src/mindustry/ai/BlockIndexer.java @@ -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. */ private GridBits[] structQuadrants; /** 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. */ private ObjectSet allOres = new ObjectSet<>(); /** Stores teams that are present here as tiles. */ private Array activeTeams = new Array<>(); /** 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. */ - 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. */ private IntMap typeMap = new IntMap<>(); /** Empty set used for returning. */ @@ -69,9 +69,9 @@ public class BlockIndexer{ Events.on(WorldLoadEvent.class, event -> { scanOres.clear(); scanOres.addAll(Item.getAllOres()); - damagedTiles = new TileArray[Team.all().length]; - flagMap = new TileArray[Team.all().length][BlockFlag.all.length]; - unitCaps = new int[Team.all().length]; + damagedTiles = new TileArray[Team.all.length]; + flagMap = new TileArray[Team.all.length][BlockFlag.all.length]; + unitCaps = new int[Team.all.length]; for(int i = 0; i < flagMap.length; i++){ for(int j = 0; j < BlockFlag.all.length; j++){ @@ -84,7 +84,7 @@ public class BlockIndexer{ ores = null; //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){ process(tile); diff --git a/core/src/mindustry/ai/Pathfinder.java b/core/src/mindustry/ai/Pathfinder.java index 5f3e2ce40b..08f3e184f2 100644 --- a/core/src/mindustry/ai/Pathfinder.java +++ b/core/src/mindustry/ai/Pathfinder.java @@ -21,18 +21,21 @@ public class Pathfinder implements Runnable{ private static final int updateFPS = 60; private static final int updateInterval = 1000 / updateFPS; private static final int impassable = -1; + private static final int fieldTimeout = 1000 * 60 * 2; /** 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 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); + /** unordered array of path data for iteration only. DO NOT iterate or access this in the main thread. */ + private Array threadList = new Array<>(), mainList = new Array<>(); + /** Maps team ID and target to to a flowfield.*/ + private ObjectMap[] fieldMap = new ObjectMap[Team.all.length]; + /** Used field maps. */ + private ObjectSet[] fieldMapUsed = new ObjectSet[Team.all.length]; /** handles task scheduling on the update thread. */ private TaskQueue queue = new TaskQueue(); - /** current pathfinding thread */ + /** Stores path target for a position. Main thread only.*/ + private ObjectMap targetCache = new ObjectMap<>(); + /** Current pathfinding thread */ private @Nullable Thread thread; public Pathfinder(){ @@ -41,16 +44,18 @@ public class Pathfinder implements Runnable{ //reset and update internal tile array tiles = new int[world.width()][world.height()]; - pathMap = new PathData[Team.all().length][PathTarget.all.length]; - created = new GridBits(Team.all().length, PathTarget.all.length); - list = new Array<>(); + fieldMap = new ObjectMap[Team.all.length]; + fieldMapUsed = new ObjectSet[Team.all.length]; + targetCache = new ObjectMap<>(); + threadList = new Array<>(); + mainList = new Array<>(); for(Tile tile : world.tiles){ tiles[tile.x][tile.y] = packTile(tile); } //special preset which may help speed things up; this is optional - preloadPath(state.rules.waveTeam, PathTarget.enemyCores); + preloadPath(state.rules.waveTeam, FlagTarget.enemyCores); start(); }); @@ -62,7 +67,7 @@ public class Pathfinder implements Runnable{ /** Packs a tile into its internal representation. */ 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. */ @@ -80,12 +85,13 @@ public class Pathfinder implements Runnable{ queue.clear(); } - public int debugValue(Team team, int x, int y){ - if(pathMap[team.id][PathTarget.enemyCores.ordinal()] == null) return 0; - return pathMap[team.id][PathTarget.enemyCores.ordinal()].weights[x][y]; - } + //public int debugValue(Team team, int x, int y){ + // if(pathMap[team.id][FlagTarget.enemyCores.ordinal()] == null) return 0; + // 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){ 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 - for(PathData[] arr : pathMap){ - for(PathData path : arr){ - if(path != null){ - synchronized(path.targets){ - path.targets.clear(); - path.target.getTargets(path.team, path.targets); - } + for(Flowfield path : mainList){ + if(path != null){ + synchronized(path.targets){ + path.targets.clear(); + path.target.getPositions(path.team, path.targets); } } } queue.post(() -> { - for(PathData data : list){ + for(Flowfield data : threadList){ updateTargets(data, x, y); } }); @@ -126,8 +130,31 @@ public class Pathfinder implements Runnable{ queue.run(); //total update time no longer than maxUpdate - for(PathData data : list){ - updateFrontier(data, maxUpdate / list.size); + for(Flowfield data : threadList){ + 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{ @@ -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. */ public Tile getTargetTile(Tile tile, Team team, PathTarget target){ 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 this combination is not found, create it on request - if(!created.get(team.id, target.ordinal())){ - created.set(team.id, target.ordinal()); + if(fieldMapUsed[team.uid].add(target)){ //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)); } 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 value = values[tile.x][tile.y]; @@ -182,6 +230,10 @@ public class Pathfinder implements Runnable{ 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. */ private boolean passable(int x, int y, Team team){ 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. * 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(path.weights[x][y] == 0){ @@ -208,10 +260,18 @@ public class Pathfinder implements Runnable{ path.weights[x][y] = impassable; } - //increment search, clear frontier - path.search++; + //clear frontier to prevent contamination 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){ //add targets 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){ - 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. * Pathfinding thread only. */ - private PathData createPath(Team team, PathTarget target, IntArray targets){ - PathData path = new PathData(team, target, world.width(), world.height()); + private Flowfield createPath(Team team, PathTarget target, IntArray targets){ + Flowfield path = new Flowfield(team, target, world.width(), world.height()); + path.lastUpdateTime = Time.millis(); - list.add(path); - pathMap[team.id][target.ordinal()] = path; + threadList.add(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 synchronized(path.targets){ @@ -263,7 +331,7 @@ public class Pathfinder implements Runnable{ } /** 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(); 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. */ - public enum PathTarget{ + public enum FlagTarget implements PathTarget{ enemyCores((team, out) -> { for(Tile other : indexer.getEnemy(team, BlockFlag.core)){ 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 targeter; - PathTarget(Cons2 targeter){ + FlagTarget(Cons2 targeter){ this.targeter = targeter; } - /** Get targets. This must run on the main thread. */ - public IntArray getTargets(Team team, IntArray out){ + @Override + public IntArray getPositions(Team team, IntArray out){ targeter.get(team, 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. */ - static class PathData{ + static class Flowfield{ /** Team this path is for. */ final Team team; /** Flag that is being targeted. */ @@ -345,8 +444,10 @@ public class Pathfinder implements Runnable{ final IntArray targets = new IntArray(); /** current search ID */ 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.target = target; @@ -363,9 +464,9 @@ public class Pathfinder implements Runnable{ short 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; + //whether it's viable to pass this block through water + boolean passableWater; } } diff --git a/core/src/mindustry/ai/types/FormationAI.java b/core/src/mindustry/ai/types/FormationAI.java index b2096ac315..0076dabbb5 100644 --- a/core/src/mindustry/ai/types/FormationAI.java +++ b/core/src/mindustry/ai/types/FormationAI.java @@ -1,6 +1,7 @@ package mindustry.ai.types; import arc.math.geom.*; +import mindustry.*; import mindustry.ai.formations.*; import mindustry.entities.units.*; import mindustry.gen.*; @@ -28,14 +29,20 @@ public class FormationAI extends AIController implements FormationMember{ if(leader.isShooting()){ unit.aimLook(leader.aimX(), leader.aimY()); }else{ - - unit.lookAt(leader.rotation()); 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 diff --git a/core/src/mindustry/ai/types/GroundAI.java b/core/src/mindustry/ai/types/GroundAI.java index 3d29310640..4169a21aec 100644 --- a/core/src/mindustry/ai/types/GroundAI.java +++ b/core/src/mindustry/ai/types/GroundAI.java @@ -38,7 +38,7 @@ public class GroundAI extends AIController{ } 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); } - protected void moveToCore(PathTarget path){ + protected void moveToCore(FlagTarget path){ Tile tile = unit.tileOn(); if(tile == null) return; Tile targetTile = pathfinder.getTargetTile(tile, unit.team(), path); @@ -86,7 +86,7 @@ public class GroundAI extends AIController{ Tile tile = unit.tileOn(); if(tile == null) return; - Tile targetTile = pathfinder.getTargetTile(tile, enemy, PathTarget.enemyCores); + Tile targetTile = pathfinder.getTargetTile(tile, enemy, FlagTarget.enemyCores); Tilec core = unit.closestCore(); if(tile == targetTile || core == null || unit.within(core, 120f)) return; diff --git a/core/src/mindustry/async/TeamIndexProcess.java b/core/src/mindustry/async/TeamIndexProcess.java index e691c50ee6..2b5c0043a6 100644 --- a/core/src/mindustry/async/TeamIndexProcess.java +++ b/core/src/mindustry/async/TeamIndexProcess.java @@ -9,8 +9,8 @@ import java.util.*; /** Creates quadtrees per unit team. */ public class TeamIndexProcess implements AsyncProcess{ - private QuadTree[] trees = new QuadTree[Team.all().length]; - private int[] counts = new int[Team.all().length]; + private QuadTree[] trees = new QuadTree[Team.all.length]; + private int[] counts = new int[Team.all.length]; public QuadTree tree(Team team){ 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 public void reset(){ - counts = new int[Team.all().length]; - trees = new QuadTree[Team.all().length]; + counts = new int[Team.all.length]; + trees = new QuadTree[Team.all.length]; } @Override public void begin(){ - for(Team team : Team.all()){ + for(Team team : Team.all){ if(trees[team.uid] != null){ trees[team.uid].clear(); } diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index b126884840..481e7b07d9 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -16,7 +16,10 @@ public class UnitTypes implements ContentList{ public static @EntityDef({Unitc.class, Mechc.class}) UnitType titan, dagger, crawler, fortress, eruptor, chaosArray, eradicator; //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 public static @EntityDef({Unitc.class, Legsc.class}) UnitType cix; diff --git a/core/src/mindustry/core/World.java b/core/src/mindustry/core/World.java index 6af7345412..e783073619 100644 --- a/core/src/mindustry/core/World.java +++ b/core/src/mindustry/core/World.java @@ -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){ byte[] dark = new byte[tiles.width * tiles.height]; byte[] writeBuffer = new byte[tiles.width * tiles.height]; diff --git a/core/src/mindustry/editor/MapEditorDialog.java b/core/src/mindustry/editor/MapEditorDialog.java index 0b15e9cbaa..e7c42c4553 100644 --- a/core/src/mindustry/editor/MapEditorDialog.java +++ b/core/src/mindustry/editor/MapEditorDialog.java @@ -529,7 +529,7 @@ public class MapEditorDialog extends Dialog implements Disposable{ int i = 0; - for(Team team : Team.base()){ + for(Team team : Team.baseTeams){ ImageButton button = new ImageButton(Tex.whiteui, Styles.clearTogglePartiali); button.margin(4f); button.getImageCell().grow(); diff --git a/core/src/mindustry/game/Team.java b/core/src/mindustry/game/Team.java index ba2565ea92..f49fea7494 100644 --- a/core/src/mindustry/game/Team.java +++ b/core/src/mindustry/game/Team.java @@ -19,9 +19,9 @@ public class Team implements Comparable{ public String name; /** 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. */ - private static final Team[] baseTeams = new Team[6]; + public static final Team[] baseTeams = new Team[6]; public final static Team derelict = new Team(0, "derelict", Color.valueOf("4d4e58")), @@ -44,16 +44,6 @@ public class Team implements Comparable{ 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){ this.name = name; this.color = color; diff --git a/core/src/mindustry/ui/fragments/HudFragment.java b/core/src/mindustry/ui/fragments/HudFragment.java index 7cc47d6a71..fcfbf2a8f5 100644 --- a/core/src/mindustry/ui/fragments/HudFragment.java +++ b/core/src/mindustry/ui/fragments/HudFragment.java @@ -183,7 +183,7 @@ public class HudFragment extends Fragment{ t.table(teams -> { teams.left(); 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)) .size(50f).margin(6f).get(); button.getImageCell().grow(); diff --git a/server/src/mindustry/server/ServerControl.java b/server/src/mindustry/server/ServerControl.java index cbdc86a6c0..96ea86692e 100644 --- a/server/src/mindustry/server/ServerControl.java +++ b/server/src/mindustry/server/ServerControl.java @@ -405,7 +405,7 @@ public class ServerControl implements ApplicationListener{ 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){ err("No team with that name found.");