From 20fbe2fbbeb6e9f865b7e129bb28f2368dc17ca7 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 5 May 2019 22:09:02 -0400 Subject: [PATCH] even less broken --- .../SerializeAnnotationProcessor.java | 14 + .../io/anuke/mindustry/ai/WaveSpawner.java | 41 +-- .../io/anuke/mindustry/content/Blocks.java | 16 +- .../anuke/mindustry/editor/MapRenderer.java | 3 +- .../anuke/mindustry/io/BaseSaveVersion.java | 248 ++++++++++++++++ .../anuke/mindustry/io/SaveFileVersion.java | 277 +++--------------- core/src/io/anuke/mindustry/io/SaveIO.java | 15 +- .../io/anuke/mindustry/io/versions/Save1.java | 1 + .../maps/generators/BasicGenerator.java | 3 +- .../maps/generators/MapGenerator.java | 21 +- .../maps/generators/RandomGenerator.java | 3 +- .../maps/zonegen/DesertWastesGenerator.java | 2 +- .../maps/zonegen/OvergrowthGenerator.java | 2 +- .../src/io/anuke/mindustry/net/NetworkIO.java | 2 - .../mindustry/world/LegacyColorMapper.java | 6 +- core/src/io/anuke/mindustry/world/Tile.java | 3 +- 16 files changed, 343 insertions(+), 314 deletions(-) create mode 100644 core/src/io/anuke/mindustry/io/BaseSaveVersion.java diff --git a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java index 4f5934fdfd..7ef43f64d8 100644 --- a/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java +++ b/annotations/src/main/java/io/anuke/annotations/SerializeAnnotationProcessor.java @@ -143,6 +143,19 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ .addStatement("bjson.writeObjectEnd()") .addStatement("stream.writeUTF(output.toString())"); + MethodSpec.Builder binaryJsonWriteStringMethod = MethodSpec.methodBuilder("write" + simpleTypeName + "StringJson") + .returns(String.class) + .addParameter(DataOutput.class, "stream") + .addParameter(type, "object") + .addException(IOException.class) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addStatement("java.io.StringWriter output = new java.io.StringWriter()") + .addStatement("bjson.setWriter(output)") + .addStatement("bjson.writeObjectStart(" + type + ".class, " + type + ".class)") + .addStatement("write" + simpleTypeName + "Json(bjson, object)") + .addStatement("bjson.writeObjectEnd()") + .addStatement("return output.toString()"); + MethodSpec.Builder binaryJsonReadMethod = MethodSpec.methodBuilder("read" + simpleTypeName + "StreamJson") .returns(type) .addParameter(DataInput.class, "stream") @@ -151,6 +164,7 @@ public class SerializeAnnotationProcessor extends AbstractProcessor{ .addStatement("return read" + simpleTypeName + "Json(bjson.fromJson(null, stream.readUTF()))"); classBuilder.addMethod(binaryJsonWriteMethod.build()); + classBuilder.addMethod(binaryJsonWriteStringMethod.build()); classBuilder.addMethod(binaryJsonReadMethod.build()); } diff --git a/core/src/io/anuke/mindustry/ai/WaveSpawner.java b/core/src/io/anuke/mindustry/ai/WaveSpawner.java index 3a1eb65fce..1be663b807 100644 --- a/core/src/io/anuke/mindustry/ai/WaveSpawner.java +++ b/core/src/io/anuke/mindustry/ai/WaveSpawner.java @@ -2,10 +2,10 @@ package io.anuke.mindustry.ai; import io.anuke.arc.Events; import io.anuke.arc.collection.Array; -import io.anuke.arc.collection.IntArray; import io.anuke.arc.math.Angles; import io.anuke.arc.math.Mathf; -import io.anuke.arc.util.*; +import io.anuke.arc.util.Time; +import io.anuke.arc.util.Tmp; import io.anuke.mindustry.content.Blocks; import io.anuke.mindustry.content.Fx; import io.anuke.mindustry.entities.Damage; @@ -14,48 +14,25 @@ import io.anuke.mindustry.entities.type.BaseUnit; import io.anuke.mindustry.game.EventType.WorldLoadEvent; import io.anuke.mindustry.game.SpawnGroup; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.world.Pos; - -import java.io.*; import static io.anuke.mindustry.Vars.*; public class WaveSpawner{ private Array flySpawns = new Array<>(); private Array groundSpawns = new Array<>(); - private IntArray loadedSpawns = new IntArray(); private boolean spawning = false; public WaveSpawner(){ Events.on(WorldLoadEvent.class, e -> reset()); } - public void write(DataOutput stream) throws IOException{ - stream.writeInt(groundSpawns.size); - for(GroundSpawn spawn : groundSpawns){ - stream.writeInt(Pos.get(spawn.x, spawn.y)); - } - } - - public void read(DataInput stream) throws IOException{ - flySpawns.clear(); - groundSpawns.clear(); - loadedSpawns.clear(); - - int amount = stream.readInt(); - - for(int i = 0; i < amount; i++){ - loadedSpawns.add(stream.readInt()); - } - } - public int countSpawns(){ return groundSpawns.size; } /** @return true if the player is near a ground spawn point. */ public boolean playerNear(){ - return groundSpawns.count(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius) > 0; + return groundSpawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius); } public void spawnEnemies(){ @@ -117,21 +94,11 @@ public class WaveSpawner{ for(int x = 0; x < world.width(); x++){ for(int y = 0; y < world.height(); y++){ - if(world.tile(x, y).block() == Blocks.spawn){ + if(world.tile(x, y).overlay() == Blocks.spawn){ addSpawns(x, y); - - //hide spawnpoints, they have served their purpose - world.tile(x, y).setBlock(Blocks.air); } } } - - for(int i = 0; i < loadedSpawns.size; i++){ - int pos = loadedSpawns.get(i); - addSpawns(Pos.x(pos), Pos.y(pos)); - } - - loadedSpawns.clear(); } private void addSpawns(int x, int y){ diff --git a/core/src/io/anuke/mindustry/content/Blocks.java b/core/src/io/anuke/mindustry/content/Blocks.java index bb200f9c6f..8b9b166877 100644 --- a/core/src/io/anuke/mindustry/content/Blocks.java +++ b/core/src/io/anuke/mindustry/content/Blocks.java @@ -88,15 +88,9 @@ public class Blocks implements ContentList{ hasShadow = false; } - public void draw(Tile tile){ - } - - public void load(){ - } - - public void init(){ - } - + public void draw(Tile tile){} + public void load(){} + public void init(){} public boolean isHidden(){ return true; } @@ -119,7 +113,9 @@ public class Blocks implements ContentList{ } } - spawn = new Block("spawn"); + spawn = new OverlayFloor("spawn"){ + public void draw(Tile tile){} + }; //Registers build blocks //no reference is needed here since they can be looked up by name later diff --git a/core/src/io/anuke/mindustry/editor/MapRenderer.java b/core/src/io/anuke/mindustry/editor/MapRenderer.java index f26b7f83f5..8be3489f1d 100644 --- a/core/src/io/anuke/mindustry/editor/MapRenderer.java +++ b/core/src/io/anuke/mindustry/editor/MapRenderer.java @@ -14,6 +14,7 @@ import io.anuke.mindustry.game.Team; import io.anuke.mindustry.graphics.IndexedRenderer; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.BlockPart; import static io.anuke.mindustry.Vars.tilesize; @@ -110,7 +111,7 @@ public class MapRenderer implements Disposable{ int idxWall = (wx % chunksize) + (wy % chunksize) * chunksize; int idxDecal = (wx % chunksize) + (wy % chunksize) * chunksize + chunksize * chunksize; - if(wall != Blocks.air && (wall.synthetic() || wall == Blocks.part)){ + if(wall != Blocks.air && (wall.synthetic() || wall instanceof BlockPart)){ region = !Core.atlas.isFound(wall.editorIcon()) ? Core.atlas.find("clear-editor") : wall.editorIcon(); if(wall.rotate){ diff --git a/core/src/io/anuke/mindustry/io/BaseSaveVersion.java b/core/src/io/anuke/mindustry/io/BaseSaveVersion.java new file mode 100644 index 0000000000..244b970593 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/BaseSaveVersion.java @@ -0,0 +1,248 @@ +package io.anuke.mindustry.io; + +import io.anuke.arc.collection.Array; +import io.anuke.arc.collection.StringMap; +import io.anuke.arc.util.Time; +import io.anuke.arc.util.io.CounterInputStream; +import io.anuke.mindustry.entities.Entities; +import io.anuke.mindustry.entities.EntityGroup; +import io.anuke.mindustry.entities.traits.*; +import io.anuke.mindustry.game.*; +import io.anuke.mindustry.type.ContentType; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; + +import java.io.*; + +import static io.anuke.mindustry.Vars.*; + +public abstract class BaseSaveVersion extends SaveFileVersion{ + + public BaseSaveVersion(int version){ + super(version); + } + + public void write(DataOutputStream stream) throws IOException{ + region("meta", stream, this::writeMeta); + region("content", stream, this::writeContentHeader); + region("map", stream, this::writeMap); + region("entities", stream, this::writeEntities); + } + + public void read(DataInputStream stream, CounterInputStream counter) throws IOException{ + region("meta", stream, counter, this::readMeta); + region("content", stream, counter, this::readContentHeader); + region("map", stream, counter, this::readMap); + region("entities", stream, counter, this::readEntities); + } + + public void writeMap(DataOutput stream) throws IOException{ + //write world size + stream.writeShort(world.width()); + stream.writeShort(world.height()); + + //floor + overlay + for(int i = 0; i < world.width() * world.height(); i++){ + Tile tile = world.tile(i % world.width(), i / world.width()); + stream.writeShort(tile.floorID()); + stream.writeShort(tile.overlayID()); + 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.floorID() != tile.floorID() || nextTile.overlayID() != tile.overlayID()){ + break; + } + + consecutives++; + } + + stream.writeByte(consecutives); + i += consecutives; + } + + //blocks + for(int i = 0; i < world.width() * world.height(); i++){ + Tile tile = world.tile(i % world.width(), i / world.width()); + stream.writeShort(tile.blockID()); + + if(tile.entity != null){ + //TODO chunks, backward compat + tile.entity.write(stream); + }else{ + //write consecutive non-entity blocks + 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.blockID() != tile.blockID()){ + break; + } + + consecutives++; + } + + stream.writeByte(consecutives); + i += consecutives; + } + } + } + + public void readMap(DataInput stream) throws IOException{ + short width = stream.readShort(); + short height = stream.readShort(); + + world.beginMapLoad(); + + Tile[][] tiles = world.createTiles(width, height); + + //read floor and create tiles first + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; + short floorid = stream.readShort(); + short oreid = stream.readShort(); + int consecutives = stream.readUnsignedByte(); + + tiles[x][y] = new Tile(x, y, floorid, oreid, (short)0); + + for(int j = i + 1; j < i + 1 + consecutives; j++){ + int newx = j % width, newy = j / width; + tiles[newx][newy] = new Tile(newx, newy, floorid, oreid, (short)0); + } + + i += consecutives; + } + + //read blocks + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; + Block block = content.block(stream.readShort()); + Tile tile = tiles[x][y]; + tile.setBlock(block); + + if(tile.entity != null){ + //TODO chunks, backward compat + tile.entity.read(stream); + }else{ + 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; + } + } + + content.setTemporaryMapper(null); + world.endMapLoad(); + } + + public void writeEntities(DataOutput stream) throws IOException{ + //write entity chunk + int groups = 0; + + for(EntityGroup group : Entities.getAllGroups()){ + if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ + groups++; + } + } + + stream.writeByte(groups); + + for(EntityGroup group : Entities.getAllGroups()){ + if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ + stream.writeInt(group.size()); + for(Entity entity : group.all()){ + //TODO chunks, backward compat + //each entity is a separate chunk. + writeChunk(stream, true, out -> { + stream.writeByte(((SaveTrait)entity).getTypeID()); + ((SaveTrait)entity).writeSave(out); + }); + } + } + } + } + + public void readEntities(DataInput stream) throws IOException{ + byte groups = stream.readByte(); + + for(int i = 0; i < groups; i++){ + int amount = stream.readInt(); + for(int j = 0; j < amount; j++){ + //TODO chunks, backwards compat + byte typeid = stream.readByte(); + SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get(); + trait.readSave(stream); + } + } + } + + public void readContentHeader(DataInput stream) throws IOException{ + + byte mapped = stream.readByte(); + + MappableContent[][] map = new MappableContent[ContentType.values().length][0]; + + for(int i = 0; i < mapped; i++){ + ContentType type = ContentType.values()[stream.readByte()]; + short total = stream.readShort(); + map[type.ordinal()] = new MappableContent[total]; + + for(int j = 0; j < total; j++){ + String name = stream.readUTF(); + map[type.ordinal()][j] = content.getByName(type, fallback.get(name, name)); + } + } + + content.setTemporaryMapper(map); + } + + public void writeContentHeader(DataOutput stream) throws IOException{ + Array[] map = content.getContentMap(); + + int mappable = 0; + for(Array arr : map){ + if(arr.size > 0 && arr.first() instanceof MappableContent){ + mappable++; + } + } + + stream.writeByte(mappable); + for(Array arr : map){ + if(arr.size > 0 && arr.first() instanceof MappableContent){ + stream.writeByte(arr.first().getContentType().ordinal()); + stream.writeShort(arr.size); + for(Content c : arr){ + stream.writeUTF(((MappableContent)c).name); + } + } + } + } + + public void writeMeta(DataOutput stream) throws IOException{ + writeStringMap(stream, StringMap.of( + "saved", Time.millis(), + "playtime", headless ? 0 : control.saves.getTotalPlaytime(), + "build", Version.build, + "mapname", world.getMap().name(), + "wave", state.wave, + "wavetime", state.wavetime//, + //"stats", Serialization.writeStatsStreamJson(state.stats), + //"rules", Serialization.writeRulesStreamJson(state.rules), + )); + } + + public void readMeta(DataInput stream) throws IOException{ + StringMap map = readStringMap(stream); + + //TODO read rules, stats + + state.wave = map.getInt("wave"); + state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing); + } +} diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileVersion.java index aedbb453bc..99f7847699 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveFileVersion.java @@ -1,40 +1,50 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.*; +import io.anuke.arc.collection.ObjectMap; import io.anuke.arc.collection.ObjectMap.Entry; +import io.anuke.arc.collection.StringMap; import io.anuke.arc.util.io.CounterInputStream; import io.anuke.arc.util.io.ReusableByteOutStream; -import io.anuke.mindustry.entities.Entities; -import io.anuke.mindustry.entities.EntityGroup; -import io.anuke.mindustry.entities.traits.*; -import io.anuke.mindustry.game.Content; -import io.anuke.mindustry.game.MappableContent; -import io.anuke.mindustry.type.ContentType; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; import java.io.*; -import static io.anuke.mindustry.Vars.content; -import static io.anuke.mindustry.Vars.world; - /** * Format: - * 1. version of format / int + * - version of format: int * (begin deflating) - * 2. regions + * - regions begin + * 1. meta tags (short length, key-val UTF pairs) + * 2. map data */ public abstract class SaveFileVersion{ public final int version; - private final ReusableByteOutStream byteOutput = new ReusableByteOutStream(); - private final DataOutputStream dataBytes = new DataOutputStream(byteOutput); - private final Region[] regions; - private final ObjectMap fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad"); + protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream(); + protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput); + protected final ObjectMap fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad"); - public SaveFileVersion(int version, Region... regions){ + public SaveFileVersion(int version){ this.version = version; - this.regions = regions; + } + + protected void region(String name, DataInput stream, CounterInputStream counter, IORunner cons) throws IOException{ + int length; + try{ + length = readChunk(stream, cons); + }catch(Throwable e){ + throw new IOException("Error reading region \"" + name + "\".", e); + } + if(length != counter.count() + 4){ + throw new IOException("Error reading region \"" + name + "\": read length mismatch. Expected: " + length + "; Actual: " + (counter.count() + 4)); + } + } + + protected void region(String name, DataOutput stream, IORunner cons) throws IOException{ + try{ + writeChunk(stream, cons); + }catch(Throwable e){ + throw new IOException("Error writing region \"" + name + "\".", e); + } } public void writeChunk(DataOutput output, IORunner runner) throws IOException{ @@ -67,7 +77,6 @@ public abstract class SaveFileVersion{ /** Reads a chunk of some length. Use the runner for reading to catch more descriptive errors. */ public int readChunk(DataInput input, boolean isByte, IORunner runner) throws IOException{ int length = isByte ? input.readUnsignedByte() : input.readInt(); - //TODO descriptive error with chunk name runner.accept(input); return length; } @@ -81,7 +90,7 @@ public abstract class SaveFileVersion{ } } - public void writeMeta(DataOutput stream, ObjectMap map) throws IOException{ + public void writeStringMap(DataOutput stream, ObjectMap map) throws IOException{ stream.writeShort(map.size); for(Entry entry : map.entries()){ stream.writeUTF(entry.key); @@ -89,7 +98,7 @@ public abstract class SaveFileVersion{ } } - public StringMap readMeta(DataInput stream) throws IOException{ + public StringMap readStringMap(DataInput stream) throws IOException{ StringMap map = new StringMap(); short size = stream.readShort(); for(int i = 0; i < size; i++){ @@ -98,227 +107,9 @@ public abstract class SaveFileVersion{ return map; } - public void writeMap(DataOutput stream) throws IOException{ - //write world size - stream.writeShort(world.width()); - stream.writeShort(world.height()); + public abstract void read(DataInputStream stream, CounterInputStream counter) throws IOException; - //floor + overlay - for(int i = 0; i < world.width() * world.height(); i++){ - Tile tile = world.tile(i % world.width(), i / world.width()); - stream.writeShort(tile.floorID()); - stream.writeShort(tile.overlayID()); - 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.floorID() != tile.floorID() || nextTile.overlayID() != tile.overlayID()){ - break; - } - - consecutives++; - } - - stream.writeByte(consecutives); - i += consecutives; - } - - //blocks - for(int i = 0; i < world.width() * world.height(); i++){ - Tile tile = world.tile(i % world.width(), i / world.width()); - stream.writeShort(tile.blockID()); - - if(tile.entity != null){ - tile.entity.write(stream); - }else{ - //write consecutive non-entity blocks - 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.blockID() != tile.blockID()){ - break; - } - - consecutives++; - } - - stream.writeByte(consecutives); - i += consecutives; - } - } - } - - public void readMap(DataInputStream stream) throws IOException{ - short width = stream.readShort(); - short height = stream.readShort(); - - world.beginMapLoad(); - - Tile[][] tiles = world.createTiles(width, height); - - //read floor and create tiles first - for(int i = 0; i < width * height; i++){ - int x = i % width, y = i / width; - short floorid = stream.readShort(); - short oreid = stream.readShort(); - int consecutives = stream.readUnsignedByte(); - - tiles[x][y] = new Tile(false, x, y, floorid, oreid); - - for(int j = i + 1; j < i + 1 + consecutives; j++){ - int newx = j % width, newy = j / width; - tiles[newx][newy] = new Tile(false, newx, newy, floorid, 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.readShort()); - Tile tile = tiles[x][y]; - tile.setBlock(block); - - if(tile.entity != null){ - tile.entity.read(stream); - }else{ - 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; - } - } - - content.setTemporaryMapper(null); - world.endMapLoad(); - } - - public void writeEntities(DataOutputStream stream) throws IOException{ - //write entity chunk - int groups = 0; - - for(EntityGroup group : Entities.getAllGroups()){ - if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ - groups++; - } - } - - stream.writeByte(groups); - - for(EntityGroup group : Entities.getAllGroups()){ - if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ - stream.writeInt(group.size()); - for(Entity entity : group.all()){ - //each entity is a separate chunk. - writeChunk(stream, true, out -> { - stream.writeByte(((SaveTrait)entity).getTypeID()); - ((SaveTrait)entity).writeSave(out); - }); - } - } - } - } - - public void readEntities(DataInputStream stream) throws IOException{ - byte groups = stream.readByte(); - - for(int i = 0; i < groups; i++){ - int amount = stream.readInt(); - for(int j = 0; j < amount; j++){ - byte typeid = stream.readByte(); - SaveTrait trait = (SaveTrait)TypeTrait.getTypeByID(typeid).get(); - trait.readSave(stream); - } - } - } - - public MappableContent[][] readContentHeader(DataInputStream stream) throws IOException{ - - byte mapped = stream.readByte(); - - MappableContent[][] map = new MappableContent[ContentType.values().length][0]; - - for(int i = 0; i < mapped; i++){ - ContentType type = ContentType.values()[stream.readByte()]; - short total = stream.readShort(); - map[type.ordinal()] = new MappableContent[total]; - - for(int j = 0; j < total; j++){ - String name = stream.readUTF(); - map[type.ordinal()][j] = content.getByName(type, fallback.get(name, name)); - } - } - - return map; - } - - public void writeContentHeader(DataOutputStream stream) throws IOException{ - Array[] map = content.getContentMap(); - - int mappable = 0; - for(Array arr : map){ - if(arr.size > 0 && arr.first() instanceof MappableContent){ - mappable++; - } - } - - stream.writeByte(mappable); - for(Array arr : map){ - if(arr.size > 0 && arr.first() instanceof MappableContent){ - stream.writeByte(arr.first().getContentType().ordinal()); - stream.writeShort(arr.size); - for(Content c : arr){ - stream.writeUTF(((MappableContent)c).name); - } - } - } - } - - public final void read(DataInputStream stream, CounterInputStream counter) throws IOException{ - for(Region region : regions){ - counter.resetCount(); - try{ - int length = readChunk(stream, region.reader); - if(length != counter.count() + 4){ - throw new IOException("Error reading region \"" + region.name + "\": read length mismatch. Expected: " + length + "; Actual: " + (counter.count() + 4)); - } - }catch(Throwable e){ - throw new IOException("Error reading region \"" + region.name + "\".", e); - } - } - } - - public final void write(DataOutputStream stream) throws IOException{ - for(Region region : regions){ - try{ - writeChunk(stream, region.writer); - }catch(Throwable e){ - throw new IOException("Error writing region \"" + region.name + "\".", e); - } - } - } - - /** A region of a save file that holds a specific category of information. - * Uses: simplify code reuse, provide better error messages, skip unnecessary data.*/ - protected final class Region{ - final IORunner writer; - final IORunner reader; - final String name; - - public Region(IORunner writer, IORunner reader, String name){ - this.writer = writer; - this.reader = reader; - this.name = name; - } - } + public abstract void write(DataOutputStream stream) throws IOException; protected interface IORunner{ void accept(T stream) throws IOException; diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index fbba038e5a..929143b82a 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -1,12 +1,14 @@ package io.anuke.mindustry.io; -import io.anuke.arc.collection.*; +import io.anuke.arc.collection.Array; +import io.anuke.arc.collection.IntMap; import io.anuke.arc.files.FileHandle; import io.anuke.arc.util.io.CounterInputStream; import io.anuke.mindustry.Vars; import io.anuke.mindustry.io.versions.Save1; import java.io.*; +import java.util.Arrays; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; @@ -110,6 +112,8 @@ public class SaveIO{ try{ stream = new DataOutputStream(os); + stream.write(header); + stream.writeInt(getVersion().version); getVersion().write(stream); stream.close(); }catch(Exception e){ @@ -135,6 +139,7 @@ public class SaveIO{ public static void load(InputStream is) throws SaveException{ try(CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){ logic.reset(); + readHeader(stream); int version = stream.readInt(); SaveFileVersion ver = versions.get(version); @@ -150,6 +155,14 @@ public class SaveIO{ return versionArray.peek(); } + public static void readHeader(DataInput input) throws IOException{ + byte[] bytes = new byte[header.length]; + input.readFully(bytes); + if(!Arrays.equals(bytes, header)){ + throw new IOException("Incorrect header! Expecting: " + Arrays.toString(header) + "; Actual: " + Arrays.toString(bytes)); + } + } + public static class SaveException extends RuntimeException{ public SaveException(Throwable throwable){ super(throwable); diff --git a/core/src/io/anuke/mindustry/io/versions/Save1.java b/core/src/io/anuke/mindustry/io/versions/Save1.java index 41563a20c9..5b6e872e49 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save1.java +++ b/core/src/io/anuke/mindustry/io/versions/Save1.java @@ -69,6 +69,7 @@ public class Save1 extends SaveFileVersion{ stream.writeFloat(state.wavetime); //wave countdown Serialization.writeStats(stream, state.stats); + world.spawner.write(stream); writeContentHeader(stream); diff --git a/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java b/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java index cf6f3cc750..375af9543a 100644 --- a/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/BasicGenerator.java @@ -156,8 +156,7 @@ public abstract class BasicGenerator extends RandomGenerator{ block = tiles[x][y].block(); ore = tiles[x][y].overlay(); r.accept(x, y); - tiles[x][y] = new Tile(x, y, floor.id, block.id); - tiles[x][y].setOverlay(ore); + tiles[x][y] = new Tile(x, y, floor.id, ore.id, block.id); } } } diff --git a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java index 6d860232c9..8f325fd8c4 100644 --- a/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/MapGenerator.java @@ -12,8 +12,7 @@ import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Loadout; import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.Floor; -import io.anuke.mindustry.world.blocks.StaticWall; +import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.storage.CoreBlock; import io.anuke.mindustry.world.blocks.storage.StorageBlock; @@ -88,12 +87,12 @@ public class MapGenerator extends Generator{ tiles[x][y].setBlock(Blocks.air); } - if(tiles[x][y].block() == Blocks.spawn && enemySpawns != -1){ + if(tiles[x][y].overlay() == Blocks.spawn && enemySpawns != -1){ enemies.add(new Point2(x, y)); - tiles[x][y].setBlock(Blocks.air); + tiles[x][y].setOverlay(Blocks.air); } - if(tiles[x][y].block() == Blocks.part){ + if(tiles[x][y].block() instanceof BlockPart){ tiles[x][y].setBlock(Blocks.air); } } @@ -111,13 +110,15 @@ public class MapGenerator extends Generator{ if(((tile.block() instanceof StaticWall && tiles[newX][newY].block() instanceof StaticWall) || (tile.block() == Blocks.air && !tiles[newX][newY].block().synthetic()) - || (tiles[newX][newY].block() == Blocks.air && tile.block() instanceof StaticWall)) && tiles[newX][newY].block() != Blocks.spawn && tile.block() != Blocks.spawn){ + || (tiles[newX][newY].block() == Blocks.air && tile.block() instanceof StaticWall))){ tile.setBlock(tiles[newX][newY].block()); } if(distortFloor){ tile.setFloor(tiles[newX][newY].floor()); - tile.setOverlay(tiles[newX][newY].overlay()); + if(tiles[newX][newY].overlay() != Blocks.spawn && tile.overlay() != Blocks.spawn){ + tile.setOverlay(tiles[newX][newY].overlay()); + } } for(Decoration decor : decorations){ @@ -150,7 +151,7 @@ public class MapGenerator extends Generator{ enemies.shuffle(); for(int i = 0; i < enemySpawns; i++){ Point2 point = enemies.get(i); - tiles[point.x][point.y].setBlock(Blocks.spawn); + tiles[point.x][point.y].setOverlay(Blocks.spawn); int rad = 10, frad = 12; @@ -160,7 +161,9 @@ public class MapGenerator extends Generator{ double dst = Mathf.dst(x, y); if(dst < frad && Structs.inBounds(wx, wy, tiles) && (dst <= rad || Mathf.chance(0.5))){ Tile tile = tiles[wx][wy]; - tile.clearOverlay(); + if(tile.overlay() != Blocks.spawn){ + tile.clearOverlay(); + } } } } diff --git a/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java b/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java index bacb5cbaea..1b29ddf9b8 100644 --- a/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java +++ b/core/src/io/anuke/mindustry/maps/generators/RandomGenerator.java @@ -26,8 +26,7 @@ public abstract class RandomGenerator extends Generator{ block = Blocks.air; ore = Blocks.air; generate(x, y); - tiles[x][y] = new Tile(x, y, floor.id, block.id); - tiles[x][y].setOverlay(ore); + tiles[x][y] = new Tile(x, y, floor.id, ore.id, block.id); } } diff --git a/core/src/io/anuke/mindustry/maps/zonegen/DesertWastesGenerator.java b/core/src/io/anuke/mindustry/maps/zonegen/DesertWastesGenerator.java index 95ee943df3..31ed7981dd 100644 --- a/core/src/io/anuke/mindustry/maps/zonegen/DesertWastesGenerator.java +++ b/core/src/io/anuke/mindustry/maps/zonegen/DesertWastesGenerator.java @@ -41,7 +41,7 @@ public class DesertWastesGenerator extends BasicGenerator{ overlay(tiles, Blocks.sand, Blocks.pebbles, 0.15f, 5, 0.8f, 30f, 0.62f); //scatter(tiles, Blocks.sandRocks, Blocks.creeptree, 1f); - tiles[endX][endY].setBlock(Blocks.spawn); + tiles[endX][endY].setOverlay(Blocks.spawn); loadout.setup(spawnX, spawnY); } } diff --git a/core/src/io/anuke/mindustry/maps/zonegen/OvergrowthGenerator.java b/core/src/io/anuke/mindustry/maps/zonegen/OvergrowthGenerator.java index c5317ac011..0fec4b4879 100644 --- a/core/src/io/anuke/mindustry/maps/zonegen/OvergrowthGenerator.java +++ b/core/src/io/anuke/mindustry/maps/zonegen/OvergrowthGenerator.java @@ -37,7 +37,7 @@ public class OvergrowthGenerator extends BasicGenerator{ noise(tiles, Blocks.darksandTaintedWater, Blocks.duneRocks, 4, 0.7f, 120f, 0.64f); //scatter(tiles, Blocks.sporePine, Blocks.whiteTreeDead, 1f); - tiles[endX][endY].setBlock(Blocks.spawn); + tiles[endX][endY].setOverlay(Blocks.spawn); loadout.setup(spawnX, spawnY); } } diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index 2239a20f05..30a11d8adc 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -40,7 +40,6 @@ public class NetworkIO{ stream.writeInt(player.id); player.write(stream); - world.spawner.write(stream); SaveIO.getSaveWriter().writeMap(stream); stream.write(Team.all.length); @@ -98,7 +97,6 @@ public class NetworkIO{ player.add(); //map - world.spawner.read(stream); SaveIO.getSaveWriter().readMap(stream); world.setMap(new Map(customMapDirectory.child(map), 0, 0, new ObjectMap<>(), true)); diff --git a/core/src/io/anuke/mindustry/world/LegacyColorMapper.java b/core/src/io/anuke/mindustry/world/LegacyColorMapper.java index 1c9e3f2ec1..17c7e19417 100644 --- a/core/src/io/anuke/mindustry/world/LegacyColorMapper.java +++ b/core/src/io/anuke/mindustry/world/LegacyColorMapper.java @@ -18,7 +18,7 @@ public class LegacyColorMapper implements ContentList{ public void load(){ defaultValue = new LegacyBlock(Blocks.stone, Blocks.air); - map("ff0000", Blocks.stone, Blocks.spawn); + map("ff0000", Blocks.stone, Blocks.air, Blocks.spawn); map("00ff00", Blocks.stone); map("323232", Blocks.stone); map("646464", Blocks.stone, Blocks.rocks); @@ -60,9 +60,7 @@ public class LegacyColorMapper implements ContentList{ public final Block ore; public LegacyBlock(Block floor, Block wall){ - this.floor = (Floor)floor; - this.wall = wall; - this.ore = null; + this(floor, wall, Blocks.air); } public LegacyBlock(Block floor, Block wall, Block ore){ diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index 147bfffd03..1ee70f24bc 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -36,10 +36,11 @@ public class Tile implements Position, TargetTrait{ block = floor = (Floor)Blocks.air; } - public Tile(boolean __removeThisLater, int x, int y, short floor, short overlay){ + public Tile(int x, int y, short floor, short overlay, short wall){ this.x = (short)x; this.y = (short)y; this.floor = (Floor)content.block(floor); + this.block = content.block(wall); this.overlay = overlay; }