Save version chunks changed to 4-byte lengths
This commit is contained in:
@@ -41,7 +41,7 @@ public class MapIO{
|
||||
SaveVersion ver = SaveIO.getSaveWriter(version);
|
||||
if(ver == null) throw new IOException("Unknown save version: " + version + ". Are you trying to load a save from a newer version?");
|
||||
StringMap tags = new StringMap();
|
||||
ver.region("meta", stream, counter, in -> tags.putAll(ver.readStringMap(in)));
|
||||
ver.readRegion("meta", stream, counter, in -> tags.putAll(ver.readStringMap(in)));
|
||||
return new Map(file, tags.getInt("width"), tags.getInt("height"), tags, custom, version, Version.build);
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public class MapIO{
|
||||
int version = stream.readInt();
|
||||
SaveVersion ver = SaveIO.getSaveWriter(version);
|
||||
if(ver == null) throw new IOException("Unknown save version: " + version + ". Are you trying to load a save from a newer version?");
|
||||
ver.region("meta", stream, counter, ver::readStringMap);
|
||||
ver.readRegion("meta", stream, counter, ver::readStringMap);
|
||||
|
||||
Pixmap floors = new Pixmap(map.width, map.height);
|
||||
Pixmap walls = new Pixmap(map.width, map.height);
|
||||
@@ -96,8 +96,8 @@ public class MapIO{
|
||||
}
|
||||
};
|
||||
|
||||
ver.region("content", stream, counter, ver::readContentHeader);
|
||||
ver.region("preview_map", stream, counter, in -> ver.readMap(in, new WorldContext(){
|
||||
ver.readRegion("content", stream, counter, ver::readContentHeader);
|
||||
ver.readRegion("preview_map", stream, counter, in -> ver.readMap(in, new WorldContext(){
|
||||
@Override public void resize(int width, int height){}
|
||||
@Override public boolean isGenerating(){return false;}
|
||||
@Override public void begin(){
|
||||
|
||||
@@ -2,7 +2,6 @@ package mindustry.io;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.struct.ObjectMap.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
@@ -75,25 +74,22 @@ public abstract class SaveFileReader{
|
||||
"slag", "molten-slag"
|
||||
);
|
||||
|
||||
protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream(), byteOutput2 = new ReusableByteOutStream();
|
||||
protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput), dataBytes2 = new DataOutputStream(byteOutput2);
|
||||
protected final ReusableByteOutStream byteOutputSmall = new ReusableByteOutStream();
|
||||
protected final DataOutputStream dataBytesSmall = new DataOutputStream(byteOutputSmall);
|
||||
protected boolean chunkNested = false;
|
||||
|
||||
protected int lastRegionLength;
|
||||
protected @Nullable CounterInputStream currCounter;
|
||||
protected static final ReusableByteOutStream byteOutput = new ReusableByteOutStream(), byteOutput2 = new ReusableByteOutStream();
|
||||
protected static final DataOutputStream dataBytes = new DataOutputStream(byteOutput), dataBytes2 = new DataOutputStream(byteOutput2);
|
||||
protected static final Writes writes1 = new Writes(dataBytes), writes2 = new Writes(dataBytes2);
|
||||
protected static final Reads chunkReads = new Reads(null);
|
||||
protected static boolean chunkNested = false;
|
||||
|
||||
public static String mapFallback(String name){
|
||||
return fallback.get(name, name);
|
||||
}
|
||||
|
||||
public void region(String name, DataInput stream, CounterInputStream counter, IORunner<DataInput> cons) throws IOException{
|
||||
//TODO: unify readRegion with readChunk, they do the same thing and both should have good error messages
|
||||
public void readRegion(String name, DataInput stream, CounterInputStream counter, IORunner<DataInput> cons) throws IOException{
|
||||
counter.resetCount();
|
||||
this.currCounter = counter;
|
||||
int length;
|
||||
try{
|
||||
length = readChunk(stream, cons);
|
||||
length = readChunk(stream, (chunkStream, len) -> cons.accept(chunkStream));
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Error reading region \"" + name + "\".", e);
|
||||
}
|
||||
@@ -103,75 +99,74 @@ public abstract class SaveFileReader{
|
||||
}
|
||||
}
|
||||
|
||||
public void region(String name, DataOutput stream, IORunner<DataOutput> cons) throws IOException{
|
||||
public void writeRegion(String name, DataOutput stream, IORunner<DataOutput> cons) throws IOException{
|
||||
try{
|
||||
writeChunk(stream, cons);
|
||||
writeChunk(stream, writes -> cons.accept(writes.output));
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Error writing region \"" + name + "\".", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeChunk(DataOutput output, IORunner<DataOutput> runner) throws IOException{
|
||||
writeChunk(output, false, runner);
|
||||
}
|
||||
|
||||
/** Write a chunk of input to the stream. An integer of some length is written first, followed by the data. */
|
||||
public void writeChunk(DataOutput output, boolean isShort, IORunner<DataOutput> runner) throws IOException{
|
||||
|
||||
//TODO awful
|
||||
public void writeChunk(DataOutput output, IORunner<Writes> runner) throws IOException{
|
||||
boolean wasNested = chunkNested;
|
||||
if(!isShort){
|
||||
chunkNested = true;
|
||||
}
|
||||
ReusableByteOutStream dout =
|
||||
isShort ? byteOutputSmall :
|
||||
wasNested ? byteOutput2 :
|
||||
byteOutput;
|
||||
|
||||
chunkNested = true;
|
||||
|
||||
//regions can be nested once, so use a different output if it's already nested
|
||||
ReusableByteOutStream dout = wasNested ? byteOutput2 : byteOutput;
|
||||
|
||||
try{
|
||||
//reset output position
|
||||
dout.reset();
|
||||
//write the needed info
|
||||
runner.accept(
|
||||
isShort ? dataBytesSmall :
|
||||
wasNested ? dataBytes2 :
|
||||
dataBytes
|
||||
);
|
||||
runner.accept(wasNested ? writes2 : writes1);
|
||||
|
||||
//write the length of the stream contents as an int, then write the contents
|
||||
int length = dout.size();
|
||||
//write length (either int or byte) followed by the output bytes
|
||||
if(!isShort){
|
||||
output.writeInt(length);
|
||||
}else{
|
||||
if(length > 65535){
|
||||
throw new IOException("Byte write length exceeded: " + length + " > 65535");
|
||||
}
|
||||
output.writeShort(length);
|
||||
}
|
||||
output.writeInt(length);
|
||||
output.write(dout.getBytes(), 0, length);
|
||||
}finally{
|
||||
chunkNested = wasNested;
|
||||
}
|
||||
}
|
||||
|
||||
public int readChunk(DataInput input, IORunner<DataInput> runner) throws IOException{
|
||||
return readChunk(input, false, runner);
|
||||
}
|
||||
|
||||
/** Reads a chunk of some length. Use the runner for reading to catch more descriptive errors. */
|
||||
public int readChunk(DataInput input, boolean isShort, IORunner<DataInput> runner) throws IOException{
|
||||
int length = isShort ? input.readUnsignedShort() : input.readInt();
|
||||
lastRegionLength = length;
|
||||
runner.accept(input);
|
||||
public int readChunk(DataInput input, IORunnerLength<DataInput> runner) throws IOException{
|
||||
//TODO: it would be really nice to support counting here to detect serialization errors
|
||||
int length = input.readInt();
|
||||
runner.accept(input, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
public void skipChunk(DataInput input) throws IOException{
|
||||
skipChunk(input, false);
|
||||
/** Reads a chunk of some length. Use the runner for reading to catch more descriptive errors. */
|
||||
public int readChunkReads(DataInput input, IORunnerLength<Reads> runner) throws IOException{
|
||||
return readChunk(input, (in, length) -> {
|
||||
chunkReads.input = in;
|
||||
runner.accept(chunkReads, length);
|
||||
});
|
||||
}
|
||||
|
||||
/** Skip a chunk completely, discarding the bytes. */
|
||||
public void skipChunk(DataInput input, boolean isShort) throws IOException{
|
||||
int length = readChunk(input, isShort, t -> {});
|
||||
public void skipChunk(DataInput input) throws IOException{
|
||||
int length = readChunk(input, (t, len) -> {});
|
||||
int skipped = input.skipBytes(length);
|
||||
if(length != skipped){
|
||||
throw new IOException("Could not skip bytes. Expected length: " + length + "; Actual length: " + skipped);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads a legacy chunk where the length is only 2 bytes. */
|
||||
public int readLegacyShortChunk(DataInput input, IORunnerLength<Reads> runner) throws IOException{
|
||||
int length = input.readUnsignedShort();
|
||||
chunkReads.input = input;
|
||||
runner.accept(chunkReads, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
/** Skip a legacy chunk completely, discarding the bytes. */
|
||||
public void skipLegacyShortChunk(DataInput input) throws IOException{
|
||||
int length = readLegacyShortChunk(input, (t, len) -> {});
|
||||
int skipped = input.skipBytes(length);
|
||||
if(length != skipped){
|
||||
throw new IOException("Could not skip bytes. Expected length: " + length + "; Actual length: " + skipped);
|
||||
@@ -203,10 +198,18 @@ public abstract class SaveFileReader{
|
||||
void accept(T stream) throws IOException;
|
||||
}
|
||||
|
||||
public interface IORunnerLength<T>{
|
||||
void accept(T stream, int length) throws IOException;
|
||||
}
|
||||
|
||||
public interface CustomChunk{
|
||||
void write(DataOutput stream) throws IOException;
|
||||
void read(DataInput stream) throws IOException;
|
||||
|
||||
default void read(DataInput stream, int length) throws IOException{
|
||||
read(stream);
|
||||
}
|
||||
|
||||
/** @return whether this chunk is enabled at all */
|
||||
default boolean shouldWrite(){
|
||||
return true;
|
||||
|
||||
@@ -20,7 +20,7 @@ public class SaveIO{
|
||||
/** Save format header. */
|
||||
public static final byte[] header = {'M', 'S', 'A', 'V'};
|
||||
public static final IntMap<SaveVersion> versions = new IntMap<>();
|
||||
public static final Seq<SaveVersion> versionArray = Seq.with(new Save1(), new Save2(), new Save3(), new Save4(), new Save5(), new Save6(), new Save7(), new Save8(), new Save9());
|
||||
public static final Seq<SaveVersion> versionArray = Seq.with(new Save1(), new Save2(), new Save3(), new Save4(), new Save5(), new Save6(), new Save7(), new Save8(), new Save9(), new Save10());
|
||||
|
||||
static{
|
||||
for(SaveVersion version : versionArray){
|
||||
|
||||
@@ -29,12 +29,6 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
|
||||
public final int version;
|
||||
|
||||
//HACK stores the last read build of the save file, valid after read meta call
|
||||
protected int lastReadBuild;
|
||||
//stores entity mappings for use after readEntityMapping
|
||||
//if null, fall back to EntityMapping's values
|
||||
protected @Nullable Prov[] entityMapping;
|
||||
|
||||
/**
|
||||
* Registers a custom save chunk reader/writer by name. This is mostly used for mods that need to save extra data.
|
||||
* @param name a mod-specific, unique name for identifying this chunk. Prefixing is recommended.
|
||||
@@ -69,26 +63,26 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
|
||||
@Override
|
||||
public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
|
||||
region("meta", stream, counter, in -> readMeta(in, context));
|
||||
region("content", stream, counter, this::readContentHeader);
|
||||
readRegion("meta", stream, counter, in -> readMeta(in, context));
|
||||
readRegion("content", stream, counter, this::readContentHeader);
|
||||
|
||||
try{
|
||||
region("map", stream, counter, in -> readMap(in, context));
|
||||
region("entities", stream, counter, this::readEntities);
|
||||
if(version >= 8) region("markers", stream, counter, this::readMarkers);
|
||||
region("custom", stream, counter, this::readCustomChunks);
|
||||
readRegion("map", stream, counter, in -> readMap(in, context));
|
||||
readRegion("entities", stream, counter, this::readEntities);
|
||||
if(version >= 8) readRegion("markers", stream, counter, this::readMarkers);
|
||||
readRegion("custom", stream, counter, this::readCustomChunks);
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(DataOutputStream stream, StringMap extraTags) throws IOException{
|
||||
region("meta", stream, out -> writeMeta(out, extraTags));
|
||||
region("content", stream, this::writeContentHeader);
|
||||
region("map", stream, this::writeMap);
|
||||
region("entities", stream, this::writeEntities);
|
||||
region("markers", stream, this::writeMarkers);
|
||||
region("custom", stream, s -> writeCustomChunks(s, false));
|
||||
writeRegion("meta", stream, out -> writeMeta(out, extraTags));
|
||||
writeRegion("content", stream, this::writeContentHeader);
|
||||
writeRegion("map", stream, this::writeMap);
|
||||
writeRegion("entities", stream, this::writeEntities);
|
||||
writeRegion("markers", stream, this::writeMarkers);
|
||||
writeRegion("custom", stream, s -> writeCustomChunks(s, false));
|
||||
}
|
||||
|
||||
public void writeCustomChunks(DataOutput stream, boolean net) throws IOException{
|
||||
@@ -98,7 +92,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
var chunk = customChunks.get(chunkName);
|
||||
stream.writeUTF(chunkName);
|
||||
|
||||
writeChunk(stream, false, chunk::write);
|
||||
writeChunk(stream, writes -> chunk.write(writes.output));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +102,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
String name = stream.readUTF();
|
||||
var chunk = customChunks.get(name);
|
||||
if(chunk != null){
|
||||
readChunk(stream, false, chunk::read);
|
||||
readChunk(stream, chunk::read);
|
||||
}else{
|
||||
skipChunk(stream);
|
||||
}
|
||||
@@ -163,7 +157,6 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
|
||||
state.mapLocales = JsonIO.read(MapLocales.class, map.get("locales", "{}"));
|
||||
if(state.rules.spawns.isEmpty()) state.rules.spawns = waves.get();
|
||||
lastReadBuild = map.getInt("build", -1);
|
||||
|
||||
if(context.getSector() != null){
|
||||
state.rules.sector = context.getSector();
|
||||
@@ -254,9 +247,9 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
if(tile.build != null){
|
||||
if(tile.isCenter()){
|
||||
stream.writeBoolean(true);
|
||||
writeChunk(stream, true, out -> {
|
||||
out.writeByte(tile.build.version());
|
||||
tile.build.writeAll(Writes.get(out));
|
||||
writeChunk(stream, out -> {
|
||||
out.b(tile.build.version());
|
||||
tile.build.writeAll(out);
|
||||
});
|
||||
}else{
|
||||
stream.writeBoolean(false);
|
||||
@@ -354,16 +347,16 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
if(isCenter){ //only read entity for center blocks
|
||||
if(block.hasBuilding()){
|
||||
try{
|
||||
readChunk(stream, true, in -> {
|
||||
byte revision = in.readByte();
|
||||
tile.build.readAll(Reads.get(in), revision);
|
||||
readChunkReads(stream, (in, len) -> {
|
||||
byte revision = in.b();
|
||||
tile.build.readAll(in, revision);
|
||||
});
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Failed to read tile entity of block: " + block, e);
|
||||
}
|
||||
}else{
|
||||
//skip the entity region, as the entity and its IO code are now gone
|
||||
skipChunk(stream, true);
|
||||
skipChunk(stream);
|
||||
}
|
||||
|
||||
context.onReadBuilding();
|
||||
@@ -393,6 +386,9 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
//write team data with entities.
|
||||
Seq<TeamData> data = state.teams.getActive().copy();
|
||||
if(!data.contains(Team.sharded.data())) data.add(Team.sharded.data());
|
||||
|
||||
Writes writes = new Writes(stream);
|
||||
|
||||
stream.writeInt(data.size);
|
||||
for(TeamData team : data){
|
||||
stream.writeInt(team.team.id);
|
||||
@@ -402,7 +398,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
stream.writeShort(block.y);
|
||||
stream.writeShort(block.rotation);
|
||||
stream.writeShort(block.block.id);
|
||||
TypeIO.writeObject(Writes.get(stream), block.config);
|
||||
TypeIO.writeObject(writes, block.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,11 +408,11 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
for(Entityc entity : Groups.all){
|
||||
if(!entity.serialize()) continue;
|
||||
|
||||
writeChunk(stream, true, out -> {
|
||||
out.writeByte(entity.classId());
|
||||
out.writeInt(entity.id());
|
||||
writeChunk(stream, out -> {
|
||||
out.b(entity.classId());
|
||||
out.i(entity.id());
|
||||
entity.beforeWrite();
|
||||
entity.write(Writes.get(out));
|
||||
entity.write(out);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -446,13 +442,14 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
public void readTeamBlocks(DataInput stream) throws IOException{
|
||||
int teamc = stream.readInt();
|
||||
|
||||
var reads = new Reads(stream);
|
||||
|
||||
for(int i = 0; i < teamc; i++){
|
||||
Team team = Team.get(stream.readInt());
|
||||
TeamData data = team.data();
|
||||
int blocks = stream.readInt();
|
||||
data.plans.clear();
|
||||
data.plans.ensureCapacity(Math.min(blocks, 1000));
|
||||
var reads = Reads.get(stream);
|
||||
var set = new IntSet();
|
||||
|
||||
for(int j = 0; j < blocks; j++){
|
||||
@@ -466,25 +463,23 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
}
|
||||
}
|
||||
|
||||
public void readWorldEntities(DataInput stream) throws IOException{
|
||||
//entityMapping is null in older save versions, so use the default
|
||||
var mapping = this.entityMapping == null ? EntityMapping.idMap : this.entityMapping;
|
||||
public void readWorldEntities(DataInput stream, Prov[] mapping) throws IOException{
|
||||
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
readChunk(stream, true, in -> {
|
||||
int typeid = in.readUnsignedByte();
|
||||
readChunkReads(stream, (in, len) -> {
|
||||
int typeid = in.ub();
|
||||
if(mapping[typeid] == null){
|
||||
in.skipBytes(lastRegionLength - 1);
|
||||
in.skip(len - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int id = in.readInt();
|
||||
int id = in.i();
|
||||
|
||||
Entityc entity = (Entityc)mapping[typeid].get();
|
||||
EntityGroup.checkNextId(id);
|
||||
entity.id(id);
|
||||
entity.read(Reads.get(in));
|
||||
entity.read(in);
|
||||
entity.add();
|
||||
});
|
||||
}
|
||||
@@ -492,9 +487,9 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
Groups.all.each(Entityc::afterReadAll);
|
||||
}
|
||||
|
||||
public void readEntityMapping(DataInput stream) throws IOException{
|
||||
public Prov[] readEntityMapping(DataInput stream) throws IOException{
|
||||
//copy entityMapping for further mutation; will be used in readWorldEntities
|
||||
entityMapping = Arrays.copyOf(EntityMapping.idMap, EntityMapping.idMap.length);
|
||||
Prov[] entityMapping = Arrays.copyOf(EntityMapping.idMap, EntityMapping.idMap.length);
|
||||
|
||||
short amount = stream.readShort();
|
||||
for(int i = 0; i < amount; i++){
|
||||
@@ -504,12 +499,14 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
String name = stream.readUTF();
|
||||
entityMapping[id] = EntityMapping.map(name);
|
||||
}
|
||||
|
||||
return entityMapping;
|
||||
}
|
||||
|
||||
public void readEntities(DataInput stream) throws IOException{
|
||||
readEntityMapping(stream);
|
||||
var mapping = readEntityMapping(stream);
|
||||
readTeamBlocks(stream);
|
||||
readWorldEntities(stream);
|
||||
readWorldEntities(stream, mapping);
|
||||
}
|
||||
|
||||
public void readContentHeader(DataInput stream) throws IOException{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import arc.util.io.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import java.io.*;
|
||||
@@ -9,7 +8,7 @@ import java.io.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** This version does not read custom chunk data (<= 6). */
|
||||
public class LegacyRegionSaveVersion extends SaveVersion{
|
||||
public class LegacyRegionSaveVersion extends ShortChunkSaveVersion{
|
||||
|
||||
public LegacyRegionSaveVersion(int version){
|
||||
super(version);
|
||||
@@ -17,12 +16,12 @@ public class LegacyRegionSaveVersion extends SaveVersion{
|
||||
|
||||
@Override
|
||||
public void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
|
||||
region("meta", stream, counter, in -> readMeta(in, context));
|
||||
region("content", stream, counter, this::readContentHeader);
|
||||
readRegion("meta", stream, counter, in -> readMeta(in, context));
|
||||
readRegion("content", stream, counter, this::readContentHeader);
|
||||
|
||||
try{
|
||||
region("map", stream, counter, in -> readMap(in, context));
|
||||
region("entities", stream, counter, this::readEntities);
|
||||
readRegion("map", stream, counter, in -> readMap(in, context));
|
||||
readRegion("entities", stream, counter, this::readEntities);
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.world.*;
|
||||
@@ -61,8 +60,8 @@ public abstract class LegacySaveVersion extends LegacyRegionSaveVersion{
|
||||
|
||||
if(block.hasBuilding()){
|
||||
try{
|
||||
readChunk(stream, true, in -> {
|
||||
byte version = in.readByte();
|
||||
readLegacyShortChunk(stream, (in, len) -> {
|
||||
byte version = in.b();
|
||||
//legacy impl of Building#read()
|
||||
tile.build.health = stream.readUnsignedShort();
|
||||
byte packedrot = stream.readByte();
|
||||
@@ -72,14 +71,14 @@ public abstract class LegacySaveVersion extends LegacyRegionSaveVersion{
|
||||
tile.setTeam(Team.get(team));
|
||||
tile.build.rotation = rotation;
|
||||
|
||||
if(tile.build.items != null) tile.build.items.read(Reads.get(stream), true);
|
||||
if(tile.build.power != null) tile.build.power.read(Reads.get(stream), true);
|
||||
if(tile.build.liquids != null) tile.build.liquids.read(Reads.get(stream), true);
|
||||
if(tile.build.items != null) tile.build.items.read(in, true);
|
||||
if(tile.build.power != null) tile.build.power.read(in, true);
|
||||
if(tile.build.liquids != null) tile.build.liquids.read(in, true);
|
||||
//skip cons.valid boolean, it's not very important here
|
||||
stream.readByte();
|
||||
|
||||
//read only from subclasses!
|
||||
tile.build.read(Reads.get(in), version);
|
||||
tile.build.read(in, version);
|
||||
});
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Failed to read tile entity of block: " + block, e);
|
||||
@@ -111,7 +110,7 @@ public abstract class LegacySaveVersion extends LegacyRegionSaveVersion{
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
//simply skip all the entities
|
||||
skipChunk(stream, true);
|
||||
skipLegacyShortChunk(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import java.io.*;
|
||||
@@ -14,21 +13,19 @@ public class LegacySaveVersion2 extends LegacyRegionSaveVersion{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readWorldEntities(DataInput stream) throws IOException{
|
||||
//entityMapping is null in older save versions, so use the default
|
||||
Prov[] mapping = this.entityMapping == null ? EntityMapping.idMap : this.entityMapping;
|
||||
public void readWorldEntities(DataInput stream, Prov[] mapping) throws IOException{
|
||||
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
readChunk(stream, true, in -> {
|
||||
int typeid = in.readUnsignedByte();
|
||||
readLegacyShortChunk(stream, (in, len) -> {
|
||||
int typeid = in.ub();
|
||||
if(mapping[typeid] == null){
|
||||
in.skipBytes(lastRegionLength - 1);
|
||||
in.skip(len - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
Entityc entity = (Entityc)mapping[typeid].get();
|
||||
entity.read(Reads.get(in));
|
||||
entity.read(in);
|
||||
entity.add();
|
||||
});
|
||||
}
|
||||
|
||||
11
core/src/mindustry/io/versions/Save10.java
Normal file
11
core/src/mindustry/io/versions/Save10.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import mindustry.io.*;
|
||||
|
||||
/** Removes short entity chunks, switching to 4 byte lengths for all chunks. */
|
||||
public class Save10 extends SaveVersion{
|
||||
|
||||
public Save10(){
|
||||
super(10);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import mindustry.gen.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/** This version only reads entities, no entity ID mappings. */
|
||||
@@ -12,7 +14,7 @@ public class Save4 extends LegacySaveVersion2{
|
||||
@Override
|
||||
public void readEntities(DataInput stream) throws IOException{
|
||||
readTeamBlocks(stream);
|
||||
readWorldEntities(stream);
|
||||
readWorldEntities(stream, EntityMapping.idMap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import mindustry.io.*;
|
||||
|
||||
public class Save7 extends SaveVersion{
|
||||
public class Save7 extends ShortChunkSaveVersion{
|
||||
|
||||
public Save7(){
|
||||
super(7);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import mindustry.io.*;
|
||||
|
||||
/** Adds support for the marker binary data region. The code is unchanged here, because it was easier to add a >= 8 check in the SaveVersion class itself. */
|
||||
public class Save8 extends SaveVersion{
|
||||
public class Save8 extends ShortChunkSaveVersion{
|
||||
|
||||
public Save8(){
|
||||
super(8);
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import mindustry.io.*;
|
||||
|
||||
/** Adds support for the new 7-byte custom tile data. This can read Save8 data, but Save8 doesn't know how to handle this version's output, thus the version change. */
|
||||
public class Save9 extends SaveVersion{
|
||||
|
||||
/** Adds support for the new 7-byte custom tile data. This can read Save8 data, but Save8 doesn't know how to handle this version's output, thus the version change. This version is the last one that uses short chunks. */
|
||||
public class Save9 extends ShortChunkSaveVersion{
|
||||
public Save9(){
|
||||
super(9);
|
||||
}
|
||||
|
||||
153
core/src/mindustry/io/versions/ShortChunkSaveVersion.java
Normal file
153
core/src/mindustry/io/versions/ShortChunkSaveVersion.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import arc.func.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class ShortChunkSaveVersion extends SaveVersion{
|
||||
|
||||
public ShortChunkSaveVersion(int version){
|
||||
super(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readWorldEntities(DataInput stream, Prov[] mapping) throws IOException{
|
||||
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
readLegacyShortChunk(stream, (in, len) -> {
|
||||
int typeid = in.ub();
|
||||
if(mapping[typeid] == null){
|
||||
in.skip(len - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int id = in.i();
|
||||
|
||||
Entityc entity = (Entityc)mapping[typeid].get();
|
||||
EntityGroup.checkNextId(id);
|
||||
entity.id(id);
|
||||
entity.read(in);
|
||||
entity.add();
|
||||
});
|
||||
}
|
||||
|
||||
Groups.all.each(Entityc::afterReadAll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readMap(DataInput stream, WorldContext context) throws IOException{
|
||||
int width = stream.readUnsignedShort();
|
||||
int height = stream.readUnsignedShort();
|
||||
|
||||
boolean generating = context.isGenerating();
|
||||
|
||||
if(!generating) context.begin();
|
||||
try{
|
||||
|
||||
context.resize(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();
|
||||
if(content.block(floorid) == Blocks.air) floorid = Blocks.stone.id;
|
||||
|
||||
context.create(x, y, floorid, oreid, (short)0);
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
context.create(newx, newy, floorid, oreid, (short)0);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//read blocks
|
||||
for(int i = 0; i < width * height; i++){
|
||||
Block block = content.block(stream.readShort());
|
||||
Tile tile = context.tile(i);
|
||||
if(block == null) block = Blocks.air;
|
||||
boolean isCenter = true;
|
||||
byte packedCheck = stream.readByte();
|
||||
boolean hadEntity = (packedCheck & 1) != 0;
|
||||
//old data format (bit 2): 1 byte only if no building is present
|
||||
//new data format (bit 3): 7 bytes (3x block-specific bytes + 1x 4-byte extra data int)
|
||||
boolean hadDataOld = (packedCheck & 2) != 0, hadDataNew = (packedCheck & 4) != 0;
|
||||
|
||||
byte data = 0, floorData = 0, overlayData = 0;
|
||||
int extraData = 0;
|
||||
|
||||
if(hadDataNew){
|
||||
data = stream.readByte();
|
||||
floorData = stream.readByte();
|
||||
overlayData = stream.readByte();
|
||||
extraData = stream.readInt();
|
||||
}
|
||||
|
||||
if(hadEntity){
|
||||
isCenter = stream.readBoolean();
|
||||
}
|
||||
|
||||
//set block only if this is the center; otherwise, it's handled elsewhere
|
||||
if(isCenter){
|
||||
tile.setBlock(block);
|
||||
}
|
||||
|
||||
//must be assigned after setBlock, because that can reset data
|
||||
if(hadDataNew){
|
||||
tile.data = data;
|
||||
tile.floorData = floorData;
|
||||
tile.overlayData = overlayData;
|
||||
tile.extraData = extraData;
|
||||
context.onReadTileData();
|
||||
}
|
||||
|
||||
if(hadEntity){
|
||||
if(isCenter){ //only read entity for center blocks
|
||||
if(block.hasBuilding()){
|
||||
try{
|
||||
readLegacyShortChunk(stream, (in, len) -> {
|
||||
byte revision = in.b();
|
||||
tile.build.readAll(in, revision);
|
||||
});
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Failed to read tile entity of block: " + block, e);
|
||||
}
|
||||
}else{
|
||||
//skip the entity region, as the entity and its IO code are now gone
|
||||
skipLegacyShortChunk(stream);
|
||||
}
|
||||
|
||||
context.onReadBuilding();
|
||||
}
|
||||
}else if(hadDataOld || hadDataNew){ //never read consecutive blocks if there's any kind of data
|
||||
if(hadDataOld){
|
||||
tile.setBlock(block);
|
||||
//the old data format was only read in the case where there is no building, and only contained a single byte
|
||||
tile.data = stream.readByte();
|
||||
}
|
||||
}else{
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
context.tile(j).setBlock(block);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
}finally{
|
||||
if(!generating) context.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user