From 20427fdffa913822d547eb55bbb4a1ceddf9a77b Mon Sep 17 00:00:00 2001 From: Anuken Date: Tue, 12 Mar 2019 10:36:04 -0400 Subject: [PATCH] Implemented correct map loading, legacy format support --- core/src/io/anuke/mindustry/Vars.java | 2 + core/src/io/anuke/mindustry/core/World.java | 2 +- .../anuke/mindustry/editor/DrawOperation.java | 25 +- .../io/anuke/mindustry/editor/EditorTile.java | 38 +- .../io/anuke/mindustry/editor/MapEditor.java | 1 + core/src/io/anuke/mindustry/io/MapIO.java | 481 ++++++++++++------ core/src/io/anuke/mindustry/io/SaveIO.java | 10 +- core/src/io/anuke/mindustry/maps/Map.java | 19 +- core/src/io/anuke/mindustry/maps/Maps.java | 2 +- .../mindustry/ui/dialogs/MapsDialog.java | 9 +- .../io/anuke/mindustry/world/CachedTile.java | 46 ++ core/src/io/anuke/mindustry/world/Tile.java | 4 +- ios/src/io/anuke/mindustry/IOSLauncher.java | 2 +- .../anuke/mindustry/server/ServerControl.java | 12 +- tools/build.gradle | 1 + 15 files changed, 474 insertions(+), 180 deletions(-) create mode 100644 core/src/io/anuke/mindustry/world/CachedTile.java diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 707f1d586c..43324c873a 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -30,6 +30,8 @@ import java.util.Locale; @SuppressWarnings("unchecked") public class Vars{ + /**IO buffer size.*/ + public static final int bufferSize = 8192; /**global charset*/ public static final Charset charset = Charset.forName("UTF-8"); /**main application name, capitalized*/ diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 440e0562d0..bdb8c7d126 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -235,7 +235,7 @@ public class World implements ApplicationListener{ try{ createTiles(map.width, map.height); - tiles = MapIO.readTiles(map, tiles); + MapIO.readTiles(map, tiles); prepareTiles(tiles); }catch(Exception e){ Log.err(e); diff --git a/core/src/io/anuke/mindustry/editor/DrawOperation.java b/core/src/io/anuke/mindustry/editor/DrawOperation.java index ef73a00ad0..c4772dabed 100755 --- a/core/src/io/anuke/mindustry/editor/DrawOperation.java +++ b/core/src/io/anuke/mindustry/editor/DrawOperation.java @@ -2,6 +2,12 @@ package io.anuke.mindustry.editor; import io.anuke.annotations.Annotations.Struct; import io.anuke.arc.collection.LongArray; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.gen.TileOp; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.Floor; + +import static io.anuke.mindustry.Vars.content; public class DrawOperation{ private LongArray array = new LongArray(); @@ -17,12 +23,28 @@ public class DrawOperation{ public void undo(MapEditor editor){ for(int i = 0; i < array.size; i++){ long l = array.get(i); + set(editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.from(l)); } } public void redo(MapEditor editor){ for(int i = 0; i < array.size; i++){ long l = array.get(i); + set(editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.to(l)); + } + } + + void set(Tile tile, byte type, byte to){ + if(type == OpType.floor.ordinal()){ + tile.setFloor((Floor)content.block(to)); + }else if(type == OpType.block.ordinal()){ + tile.setBlock(content.block(to)); + }else if(type == OpType.rotation.ordinal()){ + tile.setRotation(to); + }else if(type == OpType.team.ordinal()){ + tile.setTeam(Team.all[to]); + }else if(type == OpType.ore.ordinal()){ + tile.setOre(to); } } @@ -39,6 +61,7 @@ public class DrawOperation{ floor, block, rotation, - team + team, + ore } } diff --git a/core/src/io/anuke/mindustry/editor/EditorTile.java b/core/src/io/anuke/mindustry/editor/EditorTile.java index 8659969fb2..23159fe2ea 100644 --- a/core/src/io/anuke/mindustry/editor/EditorTile.java +++ b/core/src/io/anuke/mindustry/editor/EditorTile.java @@ -6,6 +6,10 @@ import io.anuke.mindustry.gen.TileOp; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.Floor; +import io.anuke.mindustry.world.modules.ConsumeModule; +import io.anuke.mindustry.world.modules.ItemModule; +import io.anuke.mindustry.world.modules.LiquidModule; +import io.anuke.mindustry.world.modules.PowerModule; import static io.anuke.mindustry.Vars.ui; @@ -28,7 +32,7 @@ public class EditorTile extends Tile{ Block previous = block(); if(previous == type) return; super.setBlock(type); - op(TileOp.get(x, y, (byte)OpType.floor.ordinal(), previous.id, type.id)); + op(TileOp.get(x, y, (byte)OpType.block.ordinal(), previous.id, type.id)); } @Override @@ -36,7 +40,7 @@ public class EditorTile extends Tile{ byte previous = getTeamID(); if(previous == team.ordinal()) return; super.setTeam(team); - op(TileOp.get(x, y, (byte)OpType.floor.ordinal(), previous, (byte)team.ordinal())); + op(TileOp.get(x, y, (byte)OpType.team.ordinal(), previous, (byte)team.ordinal())); } @Override @@ -44,7 +48,35 @@ public class EditorTile extends Tile{ byte previous = getRotation(); if(previous == rotation) return; super.setRotation(rotation); - op(TileOp.get(x, y, (byte)OpType.floor.ordinal(), previous, rotation)); + op(TileOp.get(x, y, (byte)OpType.rotation.ordinal(), previous, rotation)); + } + + @Override + public void setOre(byte ore){ + byte previous = getRotation(); + if(previous == ore) return; + super.setOre(ore); + op(TileOp.get(x, y, (byte)OpType.ore.ordinal(), previous, ore)); + } + + @Override + protected void preChanged(){ + super.setTeam(Team.none); + } + + @Override + protected void changed(){ + entity = null; + + Block block = block(); + + if(block.hasEntity()){ + entity = block.newEntity(); + entity.cons = new ConsumeModule(entity); + if(block.hasItems) entity.items = new ItemModule(); + if(block.hasLiquids) entity.liquids = new LiquidModule(); + if(block.hasPower) entity.power = new PowerModule(); + } } private static void op(long op){ diff --git a/core/src/io/anuke/mindustry/editor/MapEditor.java b/core/src/io/anuke/mindustry/editor/MapEditor.java index 1d03393992..91915d19ad 100644 --- a/core/src/io/anuke/mindustry/editor/MapEditor.java +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -3,6 +3,7 @@ package io.anuke.mindustry.editor; import io.anuke.arc.collection.ObjectMap; import io.anuke.arc.files.FileHandle; import io.anuke.arc.math.Mathf; +import io.anuke.arc.util.Log; import io.anuke.arc.util.Pack; import io.anuke.arc.util.Structs; import io.anuke.mindustry.content.Blocks; diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java index fdc6738c09..3c568f555a 100644 --- a/core/src/io/anuke/mindustry/io/MapIO.java +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -1,5 +1,6 @@ package io.anuke.mindustry.io; +import io.anuke.arc.collection.IntIntMap; import io.anuke.arc.collection.ObjectMap; import io.anuke.arc.collection.ObjectMap.Entry; import io.anuke.arc.files.FileHandle; @@ -13,26 +14,40 @@ import io.anuke.mindustry.game.MappableContent; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.game.Version; import io.anuke.mindustry.maps.Map; +import io.anuke.mindustry.type.ContentType; +import io.anuke.mindustry.type.Item; import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.CachedTile; import io.anuke.mindustry.world.LegacyColorMapper; import io.anuke.mindustry.world.LegacyColorMapper.LegacyBlock; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.BlockPart; +import io.anuke.mindustry.world.blocks.Floor; import java.io.*; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; +import static io.anuke.mindustry.Vars.bufferSize; import static io.anuke.mindustry.Vars.content; -import static io.anuke.mindustry.Vars.world; /** Reads and writes map files.*/ public class MapIO{ - private static final int version = 1; + public static final int version = 1; + private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; + private static ObjectMap missingBlocks; + + private static void initBlocks(){ + if(missingBlocks != null) return; + + missingBlocks = ObjectMap.of( + "stained-stone", Blocks.moss + ); + } public static boolean isImage(FileHandle file){ - try(InputStream stream = file.read()){ + try(InputStream stream = file.read(32)){ for(int i1 : pngHeader){ if(stream.read() != i1){ return false; @@ -45,8 +60,20 @@ public class MapIO{ } //TODO implement - public static Pixmap generatePreview(Map map){ - return null; + public static Pixmap generatePreview(Map map) throws IOException{ + Pixmap pixmap = new Pixmap(map.width, map.height, Format.RGBA8888); + CachedTile tile = new CachedTile(){ + @Override + protected void changed(){ + pixmap.drawPixel(x, pixmap.getHeight() - 1 - y, colorFor(floor(), block(), getTeam())); + } + }; + readTiles(map, (x, y) -> { + tile.x = (short)x; + tile.y = (short)y; + return tile; + }); + return pixmap; } public static Pixmap generatePreview(Tile[][] tiles){ @@ -60,6 +87,222 @@ public class MapIO{ return pixmap; } + public static int colorFor(Block floor, Block wall, Team team){ + if(wall.synthetic()){ + return team.intColor; + } + return Color.rgba8888(wall.solid ? wall.color : floor.color); + } + + public static void writeMap(FileHandle file, Map map, Tile[][] tiles) throws IOException{ + OutputStream output = file.write(false, bufferSize); + + { + DataOutputStream stream = new DataOutputStream(output); + stream.writeInt(version); + stream.writeInt(Version.build); + stream.writeShort(tiles.length); + stream.writeShort(tiles[0].length); + stream.writeByte((byte)map.tags.size); + + for(Entry entry : map.tags.entries()){ + stream.writeUTF(entry.key); + stream.writeUTF(entry.value); + } + } + + try(DataOutputStream stream = new DataOutputStream(new DeflaterOutputStream(output))){ + int width = map.width, height = map.height; + + SaveIO.getSaveWriter().writeContentHeader(stream); + + //floor first + for(int i = 0; i < tiles.length * tiles[0].length; i++){ + Tile tile = tiles[i % width][i / width]; + stream.writeByte(tile.getFloorID()); + stream.writeByte(tile.getOre()); + int consecutives = 0; + + for(int j = i + 1; j < width * height && consecutives < 255; j++){ + Tile nextTile = tiles[j % width][j / width]; + + if(nextTile.getFloorID() != tile.getFloorID() || nextTile.block() != Blocks.air || nextTile.getOre() != tile.getOre()){ + break; + } + + consecutives++; + } + + stream.writeByte(consecutives); + i += consecutives; + } + + //then blocks + for(int i = 0; i < tiles.length * tiles[0].length; i++){ + Tile tile = tiles[i % width][i / width]; + stream.writeByte(tile.getBlockID()); + + if(tile.block() instanceof BlockPart){ + stream.writeByte(tile.link); + }else if(tile.entity != null){ + stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation + stream.writeShort((short)tile.entity.health); //health + tile.entity.writeConfig(stream); + }else{ + //write consecutive non-entity blocks + int consecutives = 0; + + for(int j = i + 1; j < width * height && consecutives < 255; j++){ + Tile nextTile = tiles[j % width][j / width]; + + if(nextTile.block() != tile.block()){ + break; + } + + consecutives++; + } + + stream.writeByte(consecutives); + i += consecutives; + } + } + } + } + + public static Map readMap(FileHandle file, boolean custom) throws IOException{ + try(DataInputStream stream = new DataInputStream(file.read(1024))){ + ObjectMap tags = new ObjectMap<>(); + + //meta is uncompressed + int version = stream.readInt(); + if(version == 0){ + return readLegacyMap(file, custom); + } + int build = stream.readInt(); + short width = stream.readShort(), height = stream.readShort(); + byte tagAmount = stream.readByte(); + + for(int i = 0; i < tagAmount; i++){ + String name = stream.readUTF(); + String value = stream.readUTF(); + tags.put(name, value); + } + + return new Map(file, width, height, tags, custom, version, build); + } + } + + /**Reads tiles from a map, version-agnostic.*/ + public static void readTiles(Map map, Tile[][] tiles) throws IOException{ + readTiles(map, (x, y) -> tiles[x][y]); + } + + /**Reads tiles from a map, version-agnostic.*/ + public static void readTiles(Map map, TileProvider tiles) throws IOException{ + if(map.version == 0){ + readLegacyMmapTiles(map.file, tiles); + }else if(map.version == version){ + readTiles(map.file, map.width, map.height, tiles); + }else{ + throw new IOException("Unknown map version. What?"); + } + } + + /**Reads tiles from a map in the new build-65 format.*/ + private static void readTiles(FileHandle file, int width, int height, Tile[][] tiles) throws IOException{ + readTiles(file, width, height, (x, y) -> tiles[x][y]); + } + + /**Reads tiles from a map in the new build-65 format.*/ + private static void readTiles(FileHandle file, int width, int height, TileProvider tiles) throws IOException{ + try(BufferedInputStream input = file.read(bufferSize)){ + + //read map + { + DataInputStream stream = new DataInputStream(input); + + stream.readInt(); //version + stream.readInt(); //build + stream.readInt(); //width + height + byte tagAmount = stream.readByte(); + + for(int i = 0; i < tagAmount; i++){ + stream.readUTF(); + stream.readUTF(); + } + } + + try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){ + + MappableContent[][] c = SaveIO.getSaveWriter().readContentHeader(stream); + + try{ + content.setTemporaryMapper(c); + + //read floor and create tiles first + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; + byte floorid = stream.readByte(); + byte oreid = stream.readByte(); + int consecutives = stream.readUnsignedByte(); + + Tile tile = tiles.get(x, y); + tile.setFloor((Floor)content.block(floorid)); + tile.setOre(oreid); + + for(int j = i + 1; j < i + 1 + consecutives; j++){ + int newx = j % width, newy = j / width; + Tile newTile = tiles.get(newx, newy); + newTile.setFloor((Floor)content.block(floorid)); + newTile.setOre(oreid); + } + + i += consecutives; + } + + //read blocks + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; + Block block = content.block(stream.readByte()); + + Tile tile = tiles.get(x, y); + tile.setBlock(block); + + if(block == Blocks.part){ + tile.link = stream.readByte(); + }else if(tile.entity != null){ + byte tr = stream.readByte(); + short health = stream.readShort(); + + byte team = Pack.leftByte(tr); + byte rotation = Pack.rightByte(tr); + + tile.setTeam(Team.all[team]); + tile.entity.health = health; + tile.setRotation(rotation); + + tile.entity.readConfig(stream); + }else{ //no entity/part, read consecutives + int consecutives = stream.readUnsignedByte(); + + for(int j = i + 1; j < i + 1 + consecutives; j++){ + int newx = j % width, newy = j / width; + tiles.get(newx, newy).setBlock(block); + } + + i += consecutives; + } + } + + }finally{ + content.setTemporaryMapper(null); + } + } + } + } + + //region legacy IO + /**Reads a pixmap in the 3.5 pixmap format.*/ public static void readLegacyPixmap(Pixmap pixmap, Tile[][] tiles){ for(int x = 0; x < pixmap.getWidth(); x++){ @@ -96,100 +339,88 @@ public class MapIO{ } } - //TODO implement /**Reads a pixmap in the old 4.0 .mmap format.*/ - private static void readLegacyMmap(FileHandle file, Tile[][] tiles) throws IOException{ - + private static void readLegacyMmapTiles(FileHandle file, Tile[][] tiles) throws IOException{ + readLegacyMmapTiles(file, (x, y) -> tiles[x][y]); } - public static int colorFor(Block floor, Block wall, Team team){ - if(wall.synthetic()){ - return team.intColor; - } - return Color.rgba8888(wall.solid ? wall.color : floor.color); - } + //TODO implement + /**Reads a mmap in the old 4.0 .mmap format.*/ + private static void readLegacyMmapTiles(FileHandle file, TileProvider tiles) throws IOException{ + try(DataInputStream stream = new DataInputStream(file.read(bufferSize))){ + stream.readInt(); //version + byte tagAmount = stream.readByte(); - public static void writeMap(FileHandle file, Map map, Tile[][] tiles) throws IOException{ - OutputStream output = file.write(false); - - try(DataOutputStream stream = new DataOutputStream(output)){ - stream.writeInt(version); - stream.writeInt(Version.build); - stream.writeShort(tiles.length); - stream.writeShort(tiles[0].length); - stream.writeByte((byte)map.tags.size); - - for(Entry entry : map.tags.entries()){ - stream.writeUTF(entry.key); - stream.writeUTF(entry.value); - } - } - - try(DataOutputStream stream = new DataOutputStream(new DeflaterOutputStream(output))){ - - SaveIO.getSaveWriter().writeContentHeader(stream); - - //floor first - for(int i = 0; i < tiles.length * tiles[0].length; i++){ - Tile tile = world.tile(i % world.width(), i / world.width()); - stream.writeByte(tile.getFloorID()); - stream.writeByte(tile.getOre()); - int consecutives = 0; - - for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){ - Tile nextTile = world.tile(j % world.width(), j / world.width()); - - if(nextTile.getFloorID() != tile.getFloorID() || nextTile.block() != Blocks.air || nextTile.getOre() != tile.getOre()){ - break; - } - - consecutives++; - } - - stream.writeByte(consecutives); - i += consecutives; + for(int i = 0; i < tagAmount; i++){ + stream.readUTF(); //key + stream.readUTF(); //val } - //then blocks - for(int i = 0; i < tiles.length * tiles[0].length; i++){ - Tile tile = world.tile(i % world.width(), i / world.width()); - stream.writeByte(tile.getBlockID()); + initBlocks(); - if(tile.block() instanceof BlockPart){ - stream.writeByte(tile.link); - }else if(tile.entity != null){ - stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.getRotation())); //team + rotation - stream.writeShort((short)tile.entity.health); //health - tile.entity.writeConfig(stream); - }else{ - //write consecutive non-entity blocks - int consecutives = 0; + //block id -> real id map + IntIntMap map = new IntIntMap(); + IntIntMap oreMap = new IntIntMap(); - for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){ - Tile nextTile = world.tile(j % world.width(), j / world.width()); - - if(nextTile.block() != tile.block()){ - break; + short blocks = stream.readShort(); + for(int i = 0; i < blocks; i++){ + short id = stream.readShort(); + String name = stream.readUTF(); + Block block = content.getByName(ContentType.block, name); + if(block == null){ + //substitute for replacement in missingBlocks if possible + if(missingBlocks.containsKey(name)){ + block = missingBlocks.get(name); + }else if(name.startsWith("ore-")){ //an ore floor combination + String[] split = name.split("-"); + String itemName = split[1], floorName = split[2]; + Item item = content.getByName(ContentType.item, itemName); + Block floor = content.getByName(ContentType.block, floorName); + if(item != null && floor != null){ + oreMap.put(id, item.id); + block = floor; + }else{ + block = Blocks.air; } - - consecutives++; + }else{ + block = Blocks.air; } - stream.writeByte(consecutives); - i += consecutives; + } + map.put(id, block.id); + } + short width = stream.readShort(), height = stream.readShort(); + + for(int y = 0; y < height; y++){ + for(int x = 0; x < width; x++){ + Tile tile = tiles.get(x, y); + byte floorb = stream.readByte(); + byte blockb = stream.readByte(); + byte rotTeamb = stream.readByte(); + byte linkb = stream.readByte(); + stream.readByte(); //unused stuff + + tile.setFloor((Floor)content.block(map.get(floorb, 0))); + tile.setBlock(content.block(map.get(blockb, 0))); + tile.setRotation(Pack.leftByte(rotTeamb)); + if(tile.block() == Blocks.part){ + tile.setLinkByte(linkb); + } + + if(oreMap.containsKey(floorb)){ + tile.setOre((byte)oreMap.get(floorb, 0)); + } } } } } - public static Map readMap(FileHandle file, boolean custom) throws IOException{ - try(DataInputStream stream = new DataInputStream(file.read())){ + private static Map readLegacyMap(FileHandle file, boolean custom) throws IOException{ + try(DataInputStream stream = new DataInputStream(file.read(bufferSize))){ ObjectMap tags = new ObjectMap<>(); - //meta is uncompressed int version = stream.readInt(); - int build = stream.readInt(); - short width = stream.readShort(), height = stream.readShort(); + if(version != 0) throw new IOException("Attempted to read non-legacy map in legacy method!"); byte tagAmount = stream.readByte(); for(int i = 0; i < tagAmount; i++){ @@ -198,83 +429,21 @@ public class MapIO{ tags.put(name, value); } - return new Map(file, width, height, tags, custom); - } - } - - public static Tile[][] readTiles(Map map, Tile[][] tiles) throws IOException{ - return readTiles(map.file, map.width, map.height, tiles); - } - - public static Tile[][] readTiles(FileHandle file, int width, int height, Tile[][] tiles) throws IOException{ - readMap(file, false); - - try(DataInputStream stream = new DataInputStream(new InflaterInputStream(file.read()))){ - - MappableContent[][] c = SaveIO.getSaveWriter().readContentHeader(stream); - - try{ - content.setTemporaryMapper(c); - - //read floor and create tiles first - for(int i = 0; i < width * height; i++){ - int x = i % width, y = i / width; - byte floorid = stream.readByte(); - byte oreid = stream.readByte(); - int consecutives = stream.readUnsignedByte(); - - tiles[x][y] = new Tile(x, y, floorid, (byte)0); - tiles[x][y].setOre(oreid); - - for(int j = i + 1; j < i + 1 + consecutives; j++){ - int newx = j % width, newy = j / width; - Tile newTile = new Tile(newx, newy, floorid, (byte)0); - newTile.setOre(oreid); - tiles[newx][newy] = newTile; - } - - i += consecutives; - } - - //read blocks - for(int i = 0; i < width * height; i++){ - int x = i % width, y = i / width; - Block block = content.block(stream.readByte()); - - Tile tile = tiles[x][y]; - tile.setBlock(block); - - if(block == Blocks.part){ - tile.link = stream.readByte(); - }else if(tile.entity != null){ - byte tr = stream.readByte(); - short health = stream.readShort(); - - byte team = Pack.leftByte(tr); - byte rotation = Pack.rightByte(tr); - - tile.setTeam(Team.all[team]); - tile.entity.health = health; - tile.setRotation(rotation); - - tile.entity.readConfig(stream); - }else{ //no entity/part, read consecutives - int consecutives = stream.readUnsignedByte(); - - for(int j = i + 1; j < i + 1 + consecutives; j++){ - int newx = j % width, newy = j / width; - tiles[newx][newy].setBlock(block); - } - - i += consecutives; - } - } - - return tiles; - - }finally{ - content.setTemporaryMapper(null); + short blocks = stream.readShort(); + for(int i = 0; i < blocks; i++){ + stream.readShort(); + stream.readUTF(); } + short width = stream.readShort(), height = stream.readShort(); + + //note that build 64 is the default build of all maps <65; while this can be inaccurate it's better than nothing + return new Map(file, width, height, tags, custom, 0, 64); } } + + //endregion + + interface TileProvider{ + Tile get(int x, int y); + } } \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 10846a4268..f7c5b50320 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -48,7 +48,7 @@ public class SaveIO{ } public static DataInputStream getSlotStream(int slot){ - return new DataInputStream(new InflaterInputStream(fileFor(slot).read())); + return new DataInputStream(new InflaterInputStream(fileFor(slot).read(bufferSize))); } public static boolean isSaveValid(int slot){ @@ -60,7 +60,7 @@ public class SaveIO{ } public static boolean isSaveValid(FileHandle file){ - return isSaveValid(new DataInputStream(new InflaterInputStream(file.read()))); + return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize)))); } public static boolean isSaveValid(DataInputStream stream){ @@ -95,7 +95,7 @@ public class SaveIO{ } public static void write(FileHandle file){ - write(new DeflaterOutputStream(file.write(false)){ + write(new DeflaterOutputStream(file.write(false, bufferSize)){ byte[] tmp = {0}; public void write(int var1) throws IOException { @@ -120,12 +120,12 @@ public class SaveIO{ public static void load(FileHandle file) throws SaveException{ try{ //try and load; if any exception at all occurs - load(new InflaterInputStream(file.read())); + load(new InflaterInputStream(file.read(bufferSize))); }catch(SaveException e){ e.printStackTrace(); FileHandle backup = file.sibling(file.name() + "-backup." + file.extension()); if(backup.exists()){ - load(new InflaterInputStream(backup.read())); + load(new InflaterInputStream(backup.read(bufferSize))); }else{ throw new SaveException(e.getCause()); } diff --git a/core/src/io/anuke/mindustry/maps/Map.java b/core/src/io/anuke/mindustry/maps/Map.java index 715a21895f..9d9c2858d5 100644 --- a/core/src/io/anuke/mindustry/maps/Map.java +++ b/core/src/io/anuke/mindustry/maps/Map.java @@ -4,6 +4,7 @@ import io.anuke.arc.Core; import io.anuke.arc.collection.ObjectMap; import io.anuke.arc.files.FileHandle; import io.anuke.arc.graphics.Texture; +import io.anuke.mindustry.io.MapIO; public class Map{ /** Whether this is a custom map.*/ @@ -12,17 +13,31 @@ public class Map{ public final ObjectMap tags; /** Base file of this map.*/ public final FileHandle file; - /**Map width/height, shorts.*/ + /** Format version.*/ + public final int version; + /** Map width/height, shorts.*/ public int width, height; /** Preview texture.*/ public Texture texture; + /** Build that this map was created in. -1 = unknown or custom build.*/ + public int build; - public Map(FileHandle file, int width, int height, ObjectMap tags, boolean custom){ + public Map(FileHandle file, int width, int height, ObjectMap tags, boolean custom, int version, int build){ this.custom = custom; this.tags = tags; this.file = file; this.width = width; this.height = height; + this.version = version; + this.build = build; + } + + public Map(FileHandle file, int width, int height, ObjectMap tags, boolean custom, int version){ + this(file, width, height, tags, custom, version, -1); + } + + public Map(FileHandle file, int width, int height, ObjectMap tags, boolean custom){ + this(file, width, height, tags, custom, MapIO.version); } public String fileName(){ diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index ff2c7dd30f..b1644a1ed7 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -103,7 +103,7 @@ public class Maps implements Disposable{ } /** Import a map, then save it. This updates all values and stored data necessary. */ - public void importMap(FileHandle file, Map map){ + public void importMap(FileHandle file, Map map) throws IOException{ file.copyTo(customMapDirectory.child(file.name())); if(!headless){ map.texture = new Texture(MapIO.generatePreview(map)); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java index 40ad3b8405..c728375dc1 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/MapsDialog.java @@ -39,8 +39,13 @@ public class MapsDialog extends FloatingDialog{ ui.showError(Core.bundle.format("editor.import.exists", name)); }else if(conflict != null){ ui.showConfirm("$confirm", "$editor.overwrite.confirm", () -> { - world.maps.importMap(file, map); - setup(); + try{ + world.maps.importMap(file, map); + setup(); + }catch(Exception e){ + ui.showError(Core.bundle.format("editor.errorimageload", Strings.parseException(e, false))); + Log.err(e); + } }); }else{ world.maps.importMap(file, map); diff --git a/core/src/io/anuke/mindustry/world/CachedTile.java b/core/src/io/anuke/mindustry/world/CachedTile.java new file mode 100644 index 0000000000..66cc167202 --- /dev/null +++ b/core/src/io/anuke/mindustry/world/CachedTile.java @@ -0,0 +1,46 @@ +package io.anuke.mindustry.world; + +import io.anuke.arc.collection.IntMap; +import io.anuke.mindustry.entities.type.TileEntity; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.world.modules.ConsumeModule; +import io.anuke.mindustry.world.modules.ItemModule; +import io.anuke.mindustry.world.modules.LiquidModule; +import io.anuke.mindustry.world.modules.PowerModule; + +/**A tile which does not trigger change events and whose entity types are cached. + * Prevents garbage when loading previews.*/ +public class CachedTile extends Tile{ + private static IntMap entities = new IntMap<>(); + + public CachedTile(){ + super(0, 0); + } + + @Override + protected void preChanged(){ + super.setTeam(Team.none); + } + + @Override + protected void changed(){ + entity = null; + + Block block = block(); + + if(block.hasEntity()){ + //cache all entity types so only one is ever created per block type. do not add it. + if(!entities.containsKey(block.id)){ + TileEntity n = block.newEntity(); + n.cons = new ConsumeModule(entity); + if(block.hasItems) n.items = new ItemModule(); + if(block.hasLiquids) n.liquids = new LiquidModule(); + if(block.hasPower) n.power = new PowerModule(); + entities.put(block.id, n); + } + + entity = entities.get(block.id); + + } + } +} diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index d0147acda0..01554a30a3 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -361,7 +361,7 @@ public class Tile implements Position, TargetTrait{ } } - private void preChanged(){ + protected void preChanged(){ block().removed(this); if(entity != null){ entity.removeFromProximity(); @@ -369,7 +369,7 @@ public class Tile implements Position, TargetTrait{ team = 0; } - private void changed(){ + protected void changed(){ if(entity != null){ entity.remove(); entity = null; diff --git a/ios/src/io/anuke/mindustry/IOSLauncher.java b/ios/src/io/anuke/mindustry/IOSLauncher.java index cc7d8dd154..a4ce1935dd 100644 --- a/ios/src/io/anuke/mindustry/IOSLauncher.java +++ b/ios/src/io/anuke/mindustry/IOSLauncher.java @@ -113,7 +113,7 @@ public class IOSLauncher extends IOSApplication.Delegate { ui.editor.show(); } - ui.editor.beginEditMap(file.read()); + ui.editor.beginEditMap(file); }); } }); diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index fd97296a70..95720c82ee 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -144,11 +144,11 @@ public class ServerControl implements ApplicationListener{ Call.onInfoMessage((state.rules.pvp ? "[YELLOW]The " + event.winner.name() + " team is victorious![]" : "[SCARLET]Game over![]") - + "\nNext selected map:[accent] "+map.name+"[]" - + (map.meta.author() != null ? " by[accent] " + map.meta.author() + "[]" : "") + "."+ + + "\nNext selected map:[accent] "+map.name()+"[]" + + (map.author() != null ? " by[accent] " + map.author() + "[]" : "") + "."+ "\nNew game begins in " + roundExtraTime + " seconds."); - info("Selected next map to be {0}.", map.name); + info("Selected next map to be {0}.", map.name()); Map fmap = map; @@ -199,7 +199,7 @@ public class ServerControl implements ApplicationListener{ if(lastTask != null) lastTask.cancel(); - Map result = world.maps.all().find(map -> map.name.equalsIgnoreCase(arg[0])); + Map result = world.maps.all().find(map -> map.name().equalsIgnoreCase(arg[0])); if(result == null){ err("No map with name &y'{0}'&lr found.", arg[0]); @@ -252,7 +252,7 @@ public class ServerControl implements ApplicationListener{ if(!world.maps.all().isEmpty()){ info("Maps:"); for(Map map : world.maps.all()){ - info(" &ly{0}: &lb&fi{1} / {2}x{3}", map.name, map.custom ? "Custom" : "Default", map.meta.width, map.meta.height); + info(" &ly{0}: &lb&fi{1} / {2}x{3}", map.name(), map.custom ? "Custom" : "Default", map.width, map.height); } }else{ info("No maps found."); @@ -265,7 +265,7 @@ public class ServerControl implements ApplicationListener{ info("Status: &rserver closed"); }else{ info("Status:"); - info(" &lyPlaying on map &fi{0}&fb &lb/&ly Wave {1}", Strings.capitalize(world.getMap().name), state.wave); + info(" &lyPlaying on map &fi{0}&fb &lb/&ly Wave {1}", Strings.capitalize(world.getMap().name()), state.wave); if(state.rules.waves){ info("&ly {0} enemies.", unitGroups[Team.red.ordinal()].size()); diff --git a/tools/build.gradle b/tools/build.gradle index dd9ecd0cbb..11c12fa338 100644 --- a/tools/build.gradle +++ b/tools/build.gradle @@ -88,6 +88,7 @@ def antialias = {File file -> ImageIO.write(out, "png", file) } + task swapColors(){ doLast{ if (project.hasProperty("colors")) {