diff --git a/core/assets/maps/glacier.msav b/core/assets/maps/glacier.msav index 0e613a5678..603bd2aeca 100644 Binary files a/core/assets/maps/glacier.msav and b/core/assets/maps/glacier.msav differ diff --git a/core/assets/maps/veins.msav b/core/assets/maps/veins.msav new file mode 100644 index 0000000000..d29800684b Binary files /dev/null and b/core/assets/maps/veins.msav differ diff --git a/core/src/io/anuke/mindustry/ai/BlockIndexer.java b/core/src/io/anuke/mindustry/ai/BlockIndexer.java index 61500ae687..71a79a262c 100644 --- a/core/src/io/anuke/mindustry/ai/BlockIndexer.java +++ b/core/src/io/anuke/mindustry/ai/BlockIndexer.java @@ -1,29 +1,26 @@ package io.anuke.mindustry.ai; -import io.anuke.arc.Events; +import io.anuke.arc.*; import io.anuke.arc.collection.*; -import io.anuke.arc.function.Predicate; -import io.anuke.arc.math.Mathf; -import io.anuke.arc.math.geom.Geometry; -import io.anuke.mindustry.content.Blocks; -import io.anuke.mindustry.entities.type.TileEntity; -import io.anuke.mindustry.game.EventType.TileChangeEvent; -import io.anuke.mindustry.game.EventType.WorldLoadEvent; -import io.anuke.mindustry.game.Team; -import io.anuke.mindustry.game.Teams.TeamData; -import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.meta.BlockFlag; +import io.anuke.arc.function.*; +import io.anuke.arc.math.*; +import io.anuke.arc.math.geom.*; +import io.anuke.mindustry.content.*; +import io.anuke.mindustry.entities.type.*; +import io.anuke.mindustry.game.EventType.*; +import io.anuke.mindustry.game.*; +import io.anuke.mindustry.game.Teams.*; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.world.*; +import io.anuke.mindustry.world.meta.*; import static io.anuke.mindustry.Vars.*; /** Class used for indexing special target blocks for AI. */ @SuppressWarnings("unchecked") public class BlockIndexer{ - /** Size of one ore quadrant. */ - private final static int oreQuadrantSize = 20; - /** Size of one structure quadrant. */ - private final static int structQuadrantSize = 12; + /** Size of one quadrant. */ + private final static int quadrantSize = 16; /** Set of all ores that are being scanned. */ private final ObjectSet scanOres = ObjectSet.with(Item.getAllOres().toArray(Item.class)); @@ -75,7 +72,7 @@ public class BlockIndexer{ //create bitset for each team type that contains each quadrant structQuadrants = new GridBits[Team.all.length]; for(int i = 0; i < Team.all.length; i++){ - structQuadrants[i] = new GridBits(Mathf.ceil(world.width() / (float)structQuadrantSize), Mathf.ceil(world.height() / (float)structQuadrantSize)); + structQuadrants[i] = new GridBits(Mathf.ceil(world.width() / (float)quadrantSize), Mathf.ceil(world.height() / (float)quadrantSize)); } for(int x = 0; x < world.width(); x++){ @@ -94,7 +91,7 @@ public class BlockIndexer{ for(int x = 0; x < quadWidth(); x++){ for(int y = 0; y < quadHeight(); y++){ - updateQuadrant(world.tile(x * structQuadrantSize, y * structQuadrantSize)); + updateQuadrant(world.tile(x * quadrantSize, y * quadrantSize)); } } @@ -164,13 +161,13 @@ public class BlockIndexer{ TileEntity closest = null; float dst = 0; - for(int rx = Math.max((int)((x - range) / tilesize / structQuadrantSize), 0); rx <= (int)((x + range) / tilesize / structQuadrantSize) && rx < quadWidth(); rx++){ - for(int ry = Math.max((int)((y - range) / tilesize / structQuadrantSize), 0); ry <= (int)((y + range) / tilesize / structQuadrantSize) && ry < quadHeight(); ry++){ + for(int rx = Math.max((int)((x - range) / tilesize / quadrantSize), 0); rx <= (int)((x + range) / tilesize / quadrantSize) && rx < quadWidth(); rx++){ + for(int ry = Math.max((int)((y - range) / tilesize / quadrantSize), 0); ry <= (int)((y + range) / tilesize / quadrantSize) && ry < quadHeight(); ry++){ if(!getQuad(team, rx, ry)) continue; - for(int tx = rx * structQuadrantSize; tx < (rx + 1) * structQuadrantSize && tx < world.width(); tx++){ - for(int ty = ry * structQuadrantSize; ty < (ry + 1) * structQuadrantSize && ty < world.height(); ty++){ + for(int tx = rx * quadrantSize; tx < (rx + 1) * quadrantSize && tx < world.width(); tx++){ + for(int ty = ry * quadrantSize; ty < (ry + 1) * quadrantSize && ty < world.height(); ty++){ Tile other = world.ltile(tx, ty); if(other == null) continue; @@ -196,7 +193,7 @@ public class BlockIndexer{ /** * Returns a set of tiles that have ores of the specified type nearby. * While each tile in the set is not guaranteed to have an ore directly on it, - * each tile will at least have an ore within {@link #oreQuadrantSize} / 2 blocks of it. + * each tile will at least have an ore within {@link #quadrantSize} / 2 blocks of it. * Only specific ore types are scanned. See {@link #scanOres}. */ public ObjectSet getOrePositions(Item item){ @@ -205,12 +202,12 @@ public class BlockIndexer{ /** Find the closest ore block relative to a position. */ public Tile findClosestOre(float xp, float yp, Item item){ - Tile tile = Geometry.findClosest(xp, yp, world.indexer.getOrePositions(item)); + Tile tile = Geometry.findClosest(xp, yp, getOrePositions(item)); if(tile == null) return null; - for(int x = Math.max(0, tile.x - oreQuadrantSize / 2); x < tile.x + oreQuadrantSize / 2 && x < world.width(); x++){ - for(int y = Math.max(0, tile.y - oreQuadrantSize / 2); y < tile.y + oreQuadrantSize / 2 && y < world.height(); y++){ + for(int x = Math.max(0, tile.x - quadrantSize / 2); x < tile.x + quadrantSize / 2 && x < world.width(); x++){ + for(int y = Math.max(0, tile.y - quadrantSize / 2); y < tile.y + quadrantSize / 2 && y < world.height(); y++){ Tile res = world.tile(x, y); if(res.block() == Blocks.air && res.drop() == item){ return res; @@ -222,8 +219,7 @@ public class BlockIndexer{ } private void process(Tile tile){ - if(tile.block().flags.size() > 0 && - tile.getTeam() != Team.none){ + if(tile.block().flags.size() > 0 && tile.getTeam() != Team.none){ ObjectSet[] map = getFlagged(tile.getTeam()); for(BlockFlag flag : tile.block().flags){ @@ -239,16 +235,16 @@ public class BlockIndexer{ if(ores == null) return; - int quadrantX = tile.x / oreQuadrantSize; - int quadrantY = tile.y / oreQuadrantSize; + int quadrantX = tile.x / quadrantSize; + int quadrantY = tile.y / quadrantSize; itemSet.clear(); - Tile rounded = world.tile(Mathf.clamp(quadrantX * oreQuadrantSize + oreQuadrantSize / 2, 0, world.width() - 1), - Mathf.clamp(quadrantY * oreQuadrantSize + oreQuadrantSize / 2, 0, world.height() - 1)); + Tile rounded = world.tile(Mathf.clamp(quadrantX * quadrantSize + quadrantSize / 2, 0, world.width() - 1), + Mathf.clamp(quadrantY * quadrantSize + quadrantSize / 2, 0, world.height() - 1)); //find all items that this quadrant contains - for(int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++){ - for(int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++){ + for(int x = quadrantX * quadrantSize; x < world.width() && x < (quadrantX + 1) * quadrantSize; x++){ + for(int y = quadrantY * quadrantSize; y < world.height() && y < (quadrantY + 1) * quadrantSize; y++){ Tile result = world.tile(x, y); if(result == null || result.drop() == null || !scanOres.contains(result.drop())) continue; @@ -273,8 +269,8 @@ public class BlockIndexer{ if(structQuadrants == null) return; //this quadrant is now 'dirty', re-scan the whole thing - int quadrantX = tile.x / structQuadrantSize; - int quadrantY = tile.y / structQuadrantSize; + int quadrantX = tile.x / quadrantSize; + int quadrantY = tile.y / quadrantSize; int index = quadrantX + quadrantY * quadWidth(); for(Team team : Team.all){ @@ -289,8 +285,8 @@ public class BlockIndexer{ structQuadrants[data.team.ordinal()].set(quadrantX, quadrantY, false); outer: - for(int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++){ - for(int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++){ + for(int x = quadrantX * quadrantSize; x < world.width() && x < (quadrantX + 1) * quadrantSize; x++){ + for(int y = quadrantY * quadrantSize; y < world.height() && y < (quadrantY + 1) * quadrantSize; y++){ Tile result = world.ltile(x, y); //when a targetable block is found, mark this quadrant as occupied and stop searching if(result.entity != null && result.getTeam() == data.team){ @@ -307,11 +303,11 @@ public class BlockIndexer{ } private int quadWidth(){ - return Mathf.ceil(world.width() / (float)structQuadrantSize); + return Mathf.ceil(world.width() / (float)quadrantSize); } private int quadHeight(){ - return Mathf.ceil(world.height() / (float)structQuadrantSize); + return Mathf.ceil(world.height() / (float)quadrantSize); } private void scanOres(){ @@ -324,8 +320,8 @@ public class BlockIndexer{ for(int x = 0; x < world.width(); x++){ for(int y = 0; y < world.height(); y++){ - int qx = (x / oreQuadrantSize); - int qy = (y / oreQuadrantSize); + int qx = (x / quadrantSize); + int qy = (y / quadrantSize); Tile tile = world.tile(x, y); @@ -333,8 +329,8 @@ public class BlockIndexer{ if(tile.drop() != null && scanOres.contains(tile.drop()) && tile.block() == Blocks.air){ ores.get(tile.drop()).add(world.tile( //make sure to clamp quadrant middle position, since it might go off bounds - Mathf.clamp(qx * oreQuadrantSize + oreQuadrantSize / 2, 0, world.width() - 1), - Mathf.clamp(qy * oreQuadrantSize + oreQuadrantSize / 2, 0, world.height() - 1))); + Mathf.clamp(qx * quadrantSize + quadrantSize / 2, 0, world.width() - 1), + Mathf.clamp(qy * quadrantSize + quadrantSize / 2, 0, world.height() - 1))); } } } diff --git a/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java b/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java index 0005ece403..fbfd217d6b 100644 --- a/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java +++ b/core/src/io/anuke/mindustry/editor/MapGenerateDialog.java @@ -101,7 +101,7 @@ public class MapGenerateDialog extends FloatingDialog{ }}); visible(() -> generating && !updateEditorOnChange); }}).size(mobile ? 300f : 400f).padRight(10); - t.pane(p -> filterTable = p).width(300f).marginRight(6).update(pane -> { + t.pane(p -> filterTable = p.marginRight(6)).width(300f).update(pane -> { if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this){ return; } diff --git a/core/src/io/anuke/mindustry/entities/type/BaseUnit.java b/core/src/io/anuke/mindustry/entities/type/BaseUnit.java index 53ff4ede51..a8dd3b5670 100644 --- a/core/src/io/anuke/mindustry/entities/type/BaseUnit.java +++ b/core/src/io/anuke/mindustry/entities/type/BaseUnit.java @@ -1,7 +1,6 @@ package io.anuke.mindustry.entities.type; -import io.anuke.annotations.Annotations.Loc; -import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.Annotations.*; import io.anuke.arc.Core; import io.anuke.arc.graphics.g2d.Draw; import io.anuke.arc.graphics.g2d.TextureRegion; @@ -86,7 +85,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{ return type.typeID; } - public Tile getSpawner(){ + public @Nullable Tile getSpawner(){ return world.tile(spawner); } diff --git a/core/src/io/anuke/mindustry/maps/Map.java b/core/src/io/anuke/mindustry/maps/Map.java index c436b86939..d7213c8ff8 100644 --- a/core/src/io/anuke/mindustry/maps/Map.java +++ b/core/src/io/anuke/mindustry/maps/Map.java @@ -74,6 +74,10 @@ public class Map implements Comparable{ return tags.get("othercore", "true").equals("true"); } + public boolean attribute(MapAttribute attr){ + return tags.getBool(attr.name()); + } + public String author(){ return tag("author"); } diff --git a/core/src/io/anuke/mindustry/maps/MapAttribute.java b/core/src/io/anuke/mindustry/maps/MapAttribute.java new file mode 100644 index 0000000000..3df99af00e --- /dev/null +++ b/core/src/io/anuke/mindustry/maps/MapAttribute.java @@ -0,0 +1,29 @@ +package io.anuke.mindustry.maps; + +import io.anuke.arc.collection.*; +import io.anuke.arc.function.*; + +import static io.anuke.mindustry.Vars.*; + +/** Defines a specific type of attribute for a map, usually whether or not it supports a certain type of mode.*/ +public enum MapAttribute{ + /** Whether a map has a player spawnpoint in it.*/ + spawnpoint(teams -> teams.contains(defaultTeam.ordinal())), + /** Whether a map has a wave team core to attack.*/ + attack(teams -> teams.contains(waveTeam.ordinal())), + /** Whether this map supports PvP.*/ + pvp(teams -> teams.size > 1); + + private final Predicate validator; + + public static final MapAttribute[] all = values(); + + MapAttribute(Predicate set){ + this.validator = set; + } + + //todo also take into account enemy spawnpoints + public boolean validate(IntSet teams){ + return validator.test(teams); + } +} diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index d5c75467ed..08d184b6b9 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -20,7 +20,7 @@ import static io.anuke.mindustry.Vars.*; public class Maps implements Disposable{ /** List of all built-in maps. Filenames only. */ - private static String[] defaultMapNames = {"fortress", "labyrinth", "islands", "tendrils", "caldera", "glacier"}; + private static String[] defaultMapNames = {"fortress", "labyrinth", "islands", "tendrils", "caldera", "glacier", "vein"}; /** All maps stored in an ordered array. */ private Array maps = new Array<>(); /** Serializer for meta. */